SpringMVC整合Mybatis-Junit-Self4j

SpringMVC整合Mybatis

步骤:

  • 导入相关依赖
    • Spring核心依赖
    • Spring-MVC核心依赖
    • Mybatis核心依赖
      • MySQL数据库驱动依赖
        • 注意Mysql的版本和驱动版本保持一致
      • Druid数据库连接池依赖
  • Spring-Mybatis整合依赖
    • 会自动导入Spring-mabtis、Spring-jdbc和spring-tx依赖
<dependencies>

    <!--Spring核心依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.2.RELEASE</version>
    </dependency>
    <!--SpringMVC核心依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.2.RELEASE</version>
    </dependency>
    
    <!--整合Mybatis-->
    <!--1. Mysql数据库驱动依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    <!--2. 连接池依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.23</version>
    </dependency>
    <!--3. mybatis本身的依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <!--4. 整合mybatis和spring的依赖-->
    <!--MyBatis提供的和Spring进行整合的jar包-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <!--spring对jdbc封装的jar包也要导入进来,否则mybatis无法整合-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.1.2.RELEASE</version>
    </dependency>

    <!--编译时DispatcherServlet的依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
  • 在配置文件中配置数据库连接信息jdbc.properties

    • driver=com.mysql.cj.jdbc.Driver
      url=jdbc:mysql://localhost:3306/learn?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
      db-username=root
      password=root
      
  • 编写mapper接口实现对数据库操作

    • @Mapper注解可加可不加

    • @Mapper
      public interface UserMapper {
      
      
          @Select("select * from user where id=#{id}")
          User getUserById(int id);
      
          @Select("select * from user")
          List<User> getAllUser();
      
      }
      
  • 在Spring容器配置类中扫描mapper接口所在的包

  • 并通过配置文件创建数据源和sqlSession工厂,将mybatis相关对象交给spring容器进行管理

    • @Configuration
      @ComponentScan(basePackages = {"server"})
      @MapperScan(basePackages = "mapper")
      @PropertySource("classpath:jdbc.properties")
      public class SpringConfig {
      
          @Value("${driver}")
          String driver;
          @Value("${url}")
          String url;
          /*
          这里在测试的时候使用的是username,不过username被解析为默认值即电脑名称而不是配置文件中配置的值
          原因如下:
          username 和 password 被系统属性或环境变量覆盖,如果系统属性或环境变量中存在 username,Spring 会优先使用这些值,而不是从 jdbc.properties 中读取。
          */
          @Value("${db-username}")
          String username;
          @Value("${password}")
          String password;
      
          @Bean
          public DataSource dataSource(){
              DruidDataSource dataSource = new DruidDataSource();
              // 基本配置
              dataSource.setDriverClassName(driver);
              dataSource.setUrl(url);
              dataSource.setUsername(username);
              dataSource.setPassword(password);
              return dataSource;
          }
      
          @Bean
          @Autowired
          public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource){
              SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
              sqlSessionFactory.setDataSource(dataSource);
      
              /*加载xml文件的位置
              // 指定 Mapper XML 文件的位置
              sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
              );
              // 调用 getObject() 返回 SqlSessionFactory 实例
          	return sessionFactory.getObject();
              
              */
              
              
              return sqlSessionFactory;
          }
      
      
      }
      
  • 在Server相关类中注入mapper接口,调用mapper接口的方法完成对数据库的操作

    • @Service
      public class UserServer {
      
          @Autowired
          UserMapper userMapper;
      
          public String getUser(){
              User user=userMapper.getUserById(1);
              String str=user.toString();
              return str;
          }
      
      }
      

事务管理

默认行为:

  • JDBC 连接的默认行为
    • 在 JDBC 中,连接的默认行为是 自动提交事务autoCommit=true)。
    • 这意味着,如果没有显式地开启事务,每个 SQL 语句都会立即提交到数据库。
  • Spring 整合 MyBatis 的情况
    • 如果没有配置任何事务管理器(如 DataSourceTransactionManager),Spring 不会介入事务管理。
    • MyBatis 会直接使用底层的 JDBC 连接,因此每个 SQL 语句会立即提交。
  • 如果配置了事务管理器,确保事务管理器和mybatis使用的是统一个数据库源
    • 共享数据源:事务管理器和 MyBatis 通常共享同一个数据源。这样,事务管理器可以管理 MyBatis 使用的数据库连接,确保事务的一致性。
    • 事务管理器的优先级:事务管理器会覆盖 MyBatis 的默认事务行为。如果配置了事务管理器,MyBatis 会使用事务管理器管理的事务,而不是 MyBatis 的默认行为。

配置事务管理器:

  • 将事务管理器Bean配置在Spring容器中:

    • @Bean
      public PlatformTransactionManager transactionManager(DataSource dataSource) {
          return new DataSourceTransactionManager(dataSource);
      }
      
  • 启动注解驱动

    • 区分spring-config和web-config场景需要注意

      • 注意配置该注解驱动上的配置类中,配置的包扫描中要能扫到 @Transactional 注解
    • @Configuration
      @EnableTransactionManagement
      public class AppConfig {
      }
      
  • 使用 @Transactional 注解管理事务

    • @Service
      public class UserService {
      
          @Autowired
          private UserMapper userMapper;
      
          @Transactional
          public void createUser(User user) {
              userMapper.insert(user);
              throw new RuntimeException("Test exception"); // 抛出异常
          }
      }
      

事务传播行为

  • 默认行为

    • 默认的传播行为是 REQUIRED,即如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。

    • @Transactional
      public void methodA() {
          // 开启事务
          methodB(); // 调用方法 B
          // 提交或回滚事务
      }
      
      @Transactional(propagation = Propagation.REQUIRED)
      public void methodB() {
          // 在方法 A 的事务中执行
      }
      
  • 嵌套事务

    • 如果方法 A 调用方法 B,且方法 B 的传播行为是 REQUIRES_NEW,则方法 B 会开启一个新事务,方法 A 的事务会被挂起。
    • 如果方法 B 的传播行为是 NESTED,则方法 B 会在方法 A 的事务中创建一个嵌套事务。
    • REQUIRES_NEW 创建的新事务是独立的,如果方法 B 回滚,不会影响方法 A 的事务。

事务超时行为

  • 默认行为

    • 默认情况下,事务没有超时限制。
  • 设置超时

    • 可以通过 @Transactional 注解的 timeout 属性设置事务的超时时间(单位:秒)。

    • @Transactional(timeout = 10)
      public void updateUser(User user) {
          userMapper.update(user);
      }
      
    • 超时回滚:如果事务在超时时间内未完成,Spring 会回滚事务。

    • 性能影响:设置合理的超时时间,避免长时间占用数据库资源。

事务的只读属性

  • 默认行为

    • 默认情况下,事务是可读写的。
  • 设置只读

    • 可以通过 @Transactional 注解的 readOnly 属性将事务设置为只读。

    • @Transactional(readOnly = true)
      public User getUserById(int id) {
          return userMapper.selectById(id);
      }
      
    • 性能优化:只读事务可以优化数据库的性能,因为数据库不需要为写操作维护锁。

    • 写操作禁止:在只读事务中执行写操作会抛出异常。

事务的失效场景

  • @Transactional 注解只能用于 public 方法。如果将其应用于非 public 方法(如 protectedprivate 或包级私有方法),事务不会生效。

    • 确保 @Transactional 注解仅用于 public 方法。
  • 如果方法 A 调用同一个类中的方法 B,且方法 B 有 @Transactional 注解,事务不会生效。这是因为 Spring 的事务管理是基于代理的,自调用不会经过代理。

    • @Service
      public class UserService {
      
          public void createUser(User user) {
              insertUser(user); // 自调用,事务失效
          }
      
          @Transactional
          public void insertUser(User user) {
              userMapper.insert(user);
          }
      }
      
    • 解决方法

      • 将方法 B 移到另一个类中

        • @Service
          public class UserService {
          
              @Autowired
              private UserHelper userHelper;
          
              public void createUser(User user) {
                  userHelper.insertUser(user); // 通过依赖注入调用
              }
          }
          
          @Service
          public class UserHelper {
          
              @Autowired
              private UserMapper userMapper;
          
              @Transactional
              public void insertUser(User user) {
                  userMapper.insert(user);
              }
          }
          
      • 使用 AopContext.currentProxy()

        • @Service
          public class UserService {
          
              public void createUser(User user) {
                  ((UserService) AopContext.currentProxy()).insertUser(user); // 通过代理调用
              }
          
              @Transactional
              public void insertUser(User user) {
                  userMapper.insert(user);
              }
          }
          
  • 如果在事务方法中捕获了异常但没有重新抛出,事务不会回滚。默认情况下,Spring 只会在遇到运行时异常(RuntimeException)时回滚事务。

    • 在捕获异常后重新抛出,或使用 @Transactional 注解的 rollbackFor 属性指定回滚的异常类型。
  • 如果事务方法被 finalstatic 修饰,事务不会生效。这是因为 Spring 的事务管理是基于动态代理的,无法代理 finalstatic 方法。

    • JDK 动态代理基于接口实现,要求目标类必须实现至少一个接口。
    • CGLIB 动态代理基于继承实现,通过生成目标类的子类来创建代理对象。
    • 被代理的方法要求是从接口中实现的方法或者可以继承的方法,所以final方法无法被代理
    • static方法是类级别的,所以无法被代理

Junit

作用:

  • 自动加载Spring容器而无需手动加载
  • 运行demo方法

流程:

  • 导入依赖

    • <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>5.1.5.RELEASE</version>
      </dependency>
      
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
      </dependency>
      
  • 编写测试类,通过注解指定Spring容器的位置

    • 通过自动注入导入Spring容器中的Bean即可测试

    • 注意测试方法需要@Test注解

    • @RunWith(SpringJUnit4ClassRunner.class)
      //@ContextConfiguration(value = {"classpath:applicationContext.xml"})		// 指定配置文件
      @ContextConfiguration(classes = SpringConfig.class)       // 指定配置类
      public class junitTest {
      
          @Autowired
          public UserServer userServer;
          
          @Test
          public void test(){
              String user = userServer.getUser();
              System.out.println(user);
          }
      }
      

Slf4j

作用:

  • 日志框架

流程:

  • 导入相关依赖

  • <!-- Log4j2核心库 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- Log4j2 API -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.1</version>
    </dependency>
        <!-- 可选:SLF4J 绑定 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.14.1</version>
    </dependency>
    
  • 创建logger对象,该对象不需要交给Spring容器进行管理

    • import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(classes = SpringConfig.class)       // 指定配置类
      public class junitTest {
      
          private static final Logger logger = LoggerFactory.getLogger(junitTest.class);
      
          @Autowired
          public UserServer userServer;
          @Test
          public void test(){
              String user = userServer.getUser();
              System.out.println(user);
              logger.debug("This is a debug message.");
          }
      
      }
      
    • 注意:导入的一定是org.slf4j包下的相关组件

posted @ 2025-03-18 15:32  QAQ001  阅读(27)  评论(0)    收藏  举报