# 1 spring整合mybatis\&junit\&AOP\&事物 1. spring整合mybatis 2. 数据库操作中事物 3. AOP案例讲解 # 2.spring整合mybatis 大体需要做两件事 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 已查询所有用户为例 实体类对应的包名可以是: \~\~\~ entity domain pojo \~\~\~ ## 1.0整体结构 !\[image-20250924003100761\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250924003100.png) ## 1.1 依赖 \~\~\~xml org.springframework spring-context 5.3.7 com.alibaba druid 1.2.23 com.mysql mysql-connector-j 8.0.33 org.mybatis mybatis 3.5.6 org.mybatis mybatis-spring 2.0.6 org.springframework spring-jdbc 5.3.7 org.projectlombok lombok 1.18.30 junit junit 4.12 test \~\~\~ sql准备 \`\`\`sql CREATE TABLE \`tb_user\` ( \`id\` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', \`username\` VARCHAR(50) NOT NULL COMMENT '用户名', \`password\` VARCHAR(100) NOT NULL COMMENT '密码', \`age\` INT DEFAULT NULL COMMENT '年龄', \`birth\` DATETIME DEFAULT NULL COMMENT '出生日期', \`create_time\` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (\`id\`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; \`\`\` ## 1.2 实体类- User \~\~\~java @Data public class User { private Long id; private String username; private String password; private Integer age; private Date birth; private Date createTime; } \~\~\~ ## 1.3 dao层 UserDao (com.woniu.dao.UserDao) \~\~\~java public interface UserDao { public List selectAll(); } \~\~\~ UserDao.xml (com/woniu/dao/UserDao.xml) \~\~\~xml \~\~\~ ## 1.4 service层 UserService \~\~\~java public interface UserService { public List selectAll(); } \~\~\~ UserServiceImpl \~\~\~java @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public List selectAll() { return userDao.selectAll(); } \~\~\~ ## 1.5 数据库连接参数- jdbc.properties \~\~\~properties jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/woniu122 jdbc.username=root jdbc.password=123456 \~\~\~ ## 1.6 mybatis核心配置- MybatisConfig \~\~\~java package com.woniu.config; import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.Configuration; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; //@Configuration 该配置类到时候是启动配置类通过@Import导入,因此会自动注册为bean public class MybatisConfig { //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象 //@Bean修饰的方法的参数中可以拿取IOC中已存在的bean对象 @Bean public SqlSessionFactoryBean sqlSessionFactorybean(DruidDataSource druidDataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置别名扫包 sqlSessionFactoryBean.setTypeAliasesPackage("com.woniu.domain"); //配置MyBatis设置 开启驼峰、下划线命名转换 Configuration configuration = new Configuration(); configuration.setMapUnderscoreToCamelCase(true); sqlSessionFactoryBean.setConfiguration(configuration); //设置数据源 sqlSessionFactoryBean.setDataSource(druidDataSource); return sqlSessionFactoryBean; } //定义bean,返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); //设置Mapper扫包 mapperScannerConfigurer.setBasePackage("com.woniu.dao"); return mapperScannerConfigurer; } } \~\~\~ SqlSessionFactoryBean 的作用 \~\~\~ 1 创建SqlSessionFactory:SqlSessionFactoryBean是一个FactoryBean,它会创建并返回一个SqlSessionFactory实例,这是MyBatis的核心对象,用于创建SqlSession。 2 配置别名扫描:通过setTypeAliasesPackage("com.woniu.domain"),MyBatis会自动扫描该包下的所有类,并为它们创建别名(通常是类名首字母小写),这样在Mapper XML文件中可以直接使用类名而不需要全限定类名。 3 设置数据源:通过setDataSource(druidDataSource),将Druid数据源注入到MyBatis中,这样MyBatis就知道使用哪个数据库进行操作。 \~\~\~ 等价于之前的: !\[image-20250924005547104\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250924005547.png) MapperScannerConfigurer 的作用 \~\~\~ 1 自动扫描Mapper接口:通过setBasePackage("com.woniu.dao"),MapperScannerConfigurer会扫描指定包下的所有接口。 2 为接口创建代理对象:对于扫描到的每个接口(如UserDao),MapperScannerConfigurer会创建一个动态代理对象,这个代理对象实现了该接口。 3 将代理对象注册为Spring Bean:创建的代理对象会被注册到Spring容器中,这样其他组件就可以通过@Autowired注入这些Mapper接口。 \~\~\~ 等价于之前的 !\[image-20250924005613746\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250924005613.png) 为什么把以上两个对象(SqlSessionFactoryBean、MapperScannerConfigurer )放入IOC就完事呢? \~\~\~ 这是因为spring框架在后台自动完成了所有复杂的工作: 1 初始化阶段(Spring启动时): SqlSessionFactoryBean创建SqlSessionFactory MapperScannerConfigurer扫描接口并创建代理对象 代理对象被注册到Spring容器中 2 运行阶段(调用Mapper方法时): 通过@Autowired注入的是代理对象 调用代理对象的方法时,自动: 获取SqlSessionFactory 创建SqlSession 执行对应SQL 处理结果并返回 所以,我们只需要将这两个Bean放入IOC容器就"完事"了,是因为: 1 框架自动处理:Spring和MyBatis整合库在后台自动完成了所有复杂的工作 2 生命周期管理:这些组件在Spring生命周期的合适阶段自动执行 3 约定优于配置:框架根据约定自动完成大部分工作 4 控制反转:你只需要提供必要的配置,框架负责执行 5 这种设计让开发者可以专注于业务逻辑,而不需要关心底层的复杂实现细节。 \~\~\~ ## 1.7 启动配置类 -SpringConfig \~\~\~java package com.woniu.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.\*; //自己本身是一个配置类 @Configuration //扫包注册为bean @ComponentScan("com.woniu") //加载properties @PropertySource("jdbc.properties") //加载MybatisConfig配置类 @Import(MybatisConfig.class)//@Import 注解会将指定的类注册到IOC中 public class SpringConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //把数据源放入IOC中 @Bean("dataSource")//dataSource类似于bean的id,如果不指明,默认bean的id为该方法名 public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driver); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); druidDataSource.setInitialSize(5); druidDataSource.setMinIdle(5); druidDataSource.setMaxActive(20); druidDataSource.setMaxWait(60000); return druidDataSource; } } \~\~\~ ## 1.8 测试 \~\~\~java @Test public void test1(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService= context.getBean(UserService.class); List users = userService.selectAll(); System.out.println("users = " + users); } \~\~\~ ## 说明: 以前是自己通过流加载mybatis核心配置文件,通过new出来的sqlSessionFactory获得sqlSession,然后通过sqlSession拿到动态代理的mapper对象,继而执行接口方法,最终执行对应的sql语句返回结果集。 现在是将SqlSessionFactory交给Spring管理,Mapper代理对象是在Spring容器启动时,MapperScannerConfigurer会扫描指定包下的所有Mapper接口,并为每个接口创建代理对象 # 2spring整合junit ## 2.1 依赖 \~\~\~xml junit junit 4.12 test org.springframework spring-test 5.2.10.RELEASE \~\~\~ ## 2.2 UserServiceTest !\[image-20250924012537979\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250924012538.png) \~\~\~java package com.woniu; import com.woniu.config.SpringConfig; import com.woniu.domain.User; import com.woniu.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; //设置类运行器 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfig.class}) //加载启动配置类 //@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件 public class UserServiceTest { //支持依赖注入 @Autowired private UserService userService; @Test public void selectAllTest(){ List users = userService.selectAll(); System.out.println("users = " + users); } } \~\~\~ \*\*注意:\*\* \* 单元测试,如果测试的是注解配置类,则使用\`@ContextConfiguration(classes = 配置类.class)\` \* 单元测试,如果测试的是配置文件,则使用\`@ContextConfiguration(locations={配置文件名,...})\` \* Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西\`SpringJUnit4ClassRunner\` \* 上面两个配置都是固定格式,当需要测试哪个bean时,使用自动装配加载对应的对象,下面的工作就和以前做Junit单元测试完全一样了 # 3 上述完整版pom.xml \~\~\~xml 4.0.0 org.wn spring_demo1 1.0-SNAPSHOT 17 17 org.springframework spring-context 5.3.7 com.alibaba druid 1.2.23 com.mysql mysql-connector-j 8.0.33 org.mybatis mybatis 3.5.6 org.mybatis mybatis-spring 2.0.6 org.springframework spring-jdbc 5.3.7 javax.annotation javax.annotation-api 1.3.2 org.projectlombok lombok 1.18.30 junit junit 4.12 test org.springframework spring-test 5.2.10.RELEASE \~\~\~ # 4 把 SpringConfig 中读取properties到连接池相关代码移到MybatisConfig导致读取不到properties的bug 我们认为读取properties 到 连接池 应该属于mybatis范畴,不应该写在SpringConfig启动配置类中, 于是: springConfig: \~\~\~java @Configuration @ComponentScan("com.woniu") @Import(MybatisConfig.class) public class SpringConfig { } \~\~\~ MybatisConfig: \~\~\~java package com.woniu.config; import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.Configuration; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.PropertySource; @PropertySource("classpath:jdbc.properties") public class MybatisConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driver); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); druidDataSource.setInitialSize(5); druidDataSource.setMinIdle(3); druidDataSource.setMaxActive(20); druidDataSource.setMaxWait(6000); return druidDataSource; } //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象 @Bean public SqlSessionFactoryBean sqlSessionFactorybean(DruidDataSource druidDataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置别名扫包 sqlSessionFactoryBean.setTypeAliasesPackage("com.woniu.domain"); //配置MyBatis设置 开启驼峰命名转换 Configuration configuration = new Configuration(); configuration.setMapUnderscoreToCamelCase(true); sqlSessionFactoryBean.setConfiguration(configuration); //设置数据源 sqlSessionFactoryBean.setDataSource(druidDataSource); return sqlSessionFactoryBean; } //定义bean,返回MapperScannerConfigurer对象 @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); //设置Mapper扫包 mapperScannerConfigurer.setBasePackage("com.woniu.dao"); return mapperScannerConfigurer; } } 产生bug:properties无法正常读取到,导致url等4个参数是空的 可能原因:MapperScannerConfigurer 与属性占位符处理器之间可能有冲突 属性占位符处理器就是处理 properties 文件内容的组件,它负责将配置文件中的占位符(如 ${jdbc.url})替换为实际的值 \~\~\~ 解决方式: 方式1 getDataSource和mapperScannerConfigurer不要放在一个配置类 方式2:更改包扫描的时机来达到解决读取properties的bug \~\~\~java @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); //设置Mapper扫包 mapperScannerConfigurer.setBasePackage("com.woniu.dao"); return mapperScannerConfigurer; } 替换为 @Bean public static BeanFactoryPostProcessor mapperScannerConfigurer(){ return new BeanFactoryPostProcessor() { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); //设置Mapper扫包 mapperScannerConfigurer.setBasePackage("com.woniu.dao"); mapperScannerConfigurer.postProcessBeanDefinitionRegistry(registry); } }; } \~\~\~ # 5.AOP ## 5.1介绍 AOP(Aspect Oriented Programming)面向切面编程,是一种编程思想 作用:在不改原有代码的前提下对其进行增强 具体:Spring的AOP是对一个类的方法在不进行任何修改的前提下实现增强 ## 5.2 术语 连接点-JoinPoint:所有可能的拦截点--方法(理论) 切入点-Pointcut:实际被拦截的点--方法(实践) 通知-Advice:在特定的切入点执行的增强处理 通知类型: @Before:前置通知 @After:后置通知 @AfterReturning:返回通知 @AfterThrowing:异常通知 @Around:环绕通知 通知类:定义通知的类 切面-Aspect: 切入点和通知的结合,通常是一个带有 @Aspect 注解的类 !\[image-20250926114737239\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250926114737.png) ## 5.3案例:计算器 + 日志切面 spring AOP需要的依赖 \`\`\`xml org.springframework spring-aspects 5.3.7 \`\`\` applicationContext.xml \`\`\`XML \`\`\` 注解方式 \`\`\`java @Configuration // 标明这是一个 Spring 配置类 @ComponentScan("com.woniu") // 等同于 @EnableAspectJAutoProxy // 等同于 public class AppConfig { } \`\`\` 案例代码 \`\`\`JAVA package com.woniu.service; public interface Calculator { int add(int a, int b); int divide(int a, int b); } \`\`\` \`\`\`JAVA package com.woniu.service; import org.springframework.stereotype.Service; @Service // 交给 Spring 管理 public class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { int result = a + b; System.out.println("【真实方法】执行加法:" + a + "+" + b + "=" + result); return result; } @Override public int divide(int a, int b) { if (b == 0) { throw new IllegalArgumentException("除数不能为0"); } int result = a / b; System.out.println("【真实方法】执行除法:" + a + "/" + b + "=" + result); return result; } } \`\`\` 定义切入点 \| 位置 \| 示例内容 \| 含义 \| \| :--------- \| :-------------------- \| :-------------------------------------------- \| \| 关键字 \| \`execution\` \| 固定写法,表示"匹配方法执行" \| \| 第一个 \`\*\` \| \`\*\` \| \*\*返回类型\*\*:任意类型(void、int、String等) \| \| 包名+类名 \| \`com.woniu.service.\*\` \| 包名固定,\`\*\` 表示任意类 \| \| 方法名 \| \`\*\` \| 任意方法名 \| \| 参数列表 \| \`(..)\` \| 任意参数(0个或多个,任意类型) \| \`\`\`java package com.woniu.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.\*; import org.springframework.stereotype.Component; @Aspect // 告诉 Spring 这是一个切面类 @Component // 让 Spring 扫描并管理这个类 public class LoggingAspect { // 定义切入点:匹配 com.woniu.service 包下所有类的所有方法 @Pointcut("execution(\* com.woniu.service.\*.\*(..))") public void serviceMethods() {} // 前置通知:在目标方法执行前运行 @Before("serviceMethods()") public void logBefore(JoinPoint joinPoint) { System.out.println("【AOP前置】即将执行方法:" + joinPoint.getSignature().getName() + ",参数:" + java.util.Arrays.toString(joinPoint.getArgs())); } // 后置通知(方法正常结束后运行) @AfterReturning(pointcut = "serviceMethods()", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("【AOP后置】方法执行完毕:" + joinPoint.getSignature().getName() + ",返回结果:" + result); } // 异常通知(方法抛出异常时运行) @AfterThrowing(pointcut = "serviceMethods()", throwing = "error") public void logAfterThrowing(JoinPoint joinPoint, Throwable error) { System.out.println("【AOP异常】方法出错了:" + joinPoint.getSignature().getName() + ",异常信息:" + error.getMessage()); } // 最终通知(无论正常还是异常,都执行,类似 finally) @After("serviceMethods()") public void logFinally(JoinPoint joinPoint) { System.out.println("【AOP最终】方法结束了(不管有没有异常):" + joinPoint.getSignature().getName()); } // 环绕通知(最强大,可以完全控制方法执行,本例暂不展示) // 环绕通知方法:必须接收 ProceedingJoinPoint,并返回 Object // public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { // String methodName = joinPoint.getSignature().getName(); // Object\[\] args = joinPoint.getArgs(); // // System.out.println("【XML环绕-前置】即将执行 " + methodName + ",参数:" + java.util.Arrays.toString(args)); // // Object result = null; // try { // // 调用目标方法 // result = joinPoint.proceed(); // System.out.println("【XML环绕-后置】方法正常返回,结果:" + result); // } catch (Exception e) { // System.out.println("【XML环绕-异常】方法抛出异常:" + e.getMessage()); // throw e; // 可以选择重新抛出或处理 // } finally { // System.out.println("【XML环绕-最终】方法执行完毕(finally)"); // } // return result; // } } \`\`\` 测试 \`\`\`java @Test public void test2(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); //ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); // 注意:一定要从容器中获取接口类型,因为 AOP 生成的代理对象默认是基于接口的 Calculator calculator = ctx.getBean(Calculator.class); System.out.println("======= 测试正常加法 ======="); calculator.add(10, 5); System.out.println("\\n======= 测试除法(正常) ======="); calculator.divide(20, 4); System.out.println("\\n======= 测试除法(异常情况) ======="); try { calculator.divide(10, 0); } catch (Exception e) { System.out.println("【主程序捕获】" + e.getMessage()); } } \`\`\` ## 5.4拦截自定义注解 ### 1.新建自定义注解类 \`\`\`java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno { String value() ; } \`\`\` ### 2.切面类:MyAspect,以后置通知为例 \`\`\`java @Pointcut("@annotation(com.woniu.anno.MyAnno)") public void userServiceAnno(){} @AfterReturning(pointcut = "userServiceAnno()",returning = "result") public void myBusiness3(JoinPoint joinPoint,Object result) throws Throwable { System.out.println("进入后置通知3"); //获得参数 Object\[\] args = joinPoint.getArgs(); System.out.println("args = " + Arrays.toString(args)); //获得连接点(目标方法名) System.out.println("方法名="+joinPoint.getSignature().getName()); //获取目标方法的返回值 System.out.println("result = " + result); //获得自定义注解的属性 // 获取目标方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 获取注解实例 MyAnno myAnno = method.getAnnotation(MyAnno.class); System.out.println("自定义注解属性="+myAnno.value()); } \`\`\` ### 3.在目标方法上添加自定义注解 将注解加在你需要的地方 \`\`\`java @MyAnno("woniu") @Override public List selectAll() { List list = userDao.selectAll(); return list; } \`\`\` # 6.事务 Spring 在没有显式配置事务的情况下会自动提交事务,无需手动调用 commit() 方法 如果在一个业务中,需要保证多组sql操作的原子性,需要添加 @Transactional @Transactional可以作用于方法和类上,如果是类,则代表该类下所有方法都生效 推荐在具体的方法上添加@Transactional 下面代码是手动提交事务代码案例 \`\`\`java @Bean("dataSource")//dataSource类似于bean的id,如果不指明,默认bean的id为该方法名 public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driver); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); druidDataSource.setInitialSize(5); druidDataSource.setMinIdle(5); druidDataSource.setMaxActive(20); druidDataSource.setMaxWait(60000); return druidDataSource; } @Bean public DataSourceTransactionManager transactionManager(){ return new DataSourceTransactionManager(getDataSource()); } @Service public class OrderService { @Autowired private DataSourceTransactionManager transactionManager; @Autowired private OrderMapper orderMapper; public void createOrder(Order order) { // 1. 定义事务属性 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // 设置事务隔离级别和传播行为 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 2. 开启事务,获取事务状态 TransactionStatus status = transactionManager.getTransaction(def); try { // 3. 执行业务操作 orderMapper.insertOrder(order); // ... 其他数据库操作 // 4. 手动提交事务 transactionManager.commit(status); } catch (Exception e) { // 5. 发生异常,手动回滚事务 transactionManager.rollback(status); // 根据需要决定是否抛出异常 throw e; } } } \`\`\` ## 6.1PlatformTransactionManager spring中管理事务的核心组件:PlatformTransactionManager:Spring事务管理的核心接口,负责管理事务的生命周期,包括开启、提交和回滚事务等操作。 事务一般用于service层 ## 6.2 开启事务管理 通过@EnableTransactionManagement + 把PlatformTransactionManager放入IOC来实现 SpringConfig: \~\~\~java @EnableTransactionManagement//启用了事务管理的功能 public class SpringConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { //DataSourceTransactionManager是PlatformTransactionManager的一个实现类 //它专门用于管理基于DataSource的数据源事务,通过构造函数注入DataSource,与具体的数据库连接关联 return new DataSourceTransactionManager(dataSource); } } \~\~\~ ## 6.3 事务分类 1 声明式事务 在service实现类中某个方法上写@Transactional ,则该方法会自动用到事务 在service实现类上写@Transactional ,则该类下所有方法都会自动用到事务 2 编程式事务 通过TransactionTemplate在代码中手动管理事务----细粒度更精确,但比声明式麻烦 ## 6.4 声明式事务(需要开启事务管理) @Transactional 注解标记了方法需要事务管理 Spring框架会从IOC容器中获取之前注册的 PlatformTransactionManager 实例 当方法执行时,PlatformTransactionManager 负责开启事务、提交或回滚事务 如果方法执行过程中出现异常,事务管理器会自动回滚事务,保证数据一致性 提供UserDao.java+UserDao.xml \~\~\~java public interface UserDao { void save(User user); } \~\~\~ \~\~\~xml insert into tb_user (username, password, age, birth, create_time) values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{birth,jdbcType=TIMESTAMP}, now()) \~\~\~ 提供BookDao.java+BookDao.xml \~\~\~java public interface BookDao { void save(Book book); } \~\~\~ \~\~\~xml insert into tb_book (book_name, book_price, book_desc, type_id, img, create_time) values (#{bookName,jdbcType=VARCHAR}, #{bookPrice,jdbcType=DECIMAL}, #{bookDesc,jdbcType=VARCHAR}, #{typeId,jdbcType=INTEGER}, #{img,jdbcType=VARCHAR}, now()) \~\~\~ UserServiceImpl: 当没有@Transactional时,用户添加成功,书籍添加失败 当有@Transactional时,用户添和书籍都会添加失败,证明@Transactional事务具有原子性,要么都成功,要么都失败 \~\~\~ @Transactional @Override public void testTr() { //添加用户 User u=new User();u.setUsername("www2"); userDao.save(u); //模拟异常 int a=1/0; //添加书籍 Book b=new Book();b.setBookName("www2"); bookDao.save(b); } \~\~\~ ## 6.5 编程式事务(需要开启事务管理) 1 提供更精细的控制能力,通过代码显式控制事务边界 2 可以在事务中根据业务逻辑决定是否回滚:status.setRollbackOnly() 3 需要手动把TransactionTemplate放入IOC中 SpringConfig: \~\~\~java @Configuration @ComponentScan({"com.woniu.service","com.woniu.mapper"}) @Import({MybatisConfig.class,JdbcConfig.class})//@Import 注解会将指定的类注册到IOC中 @EnableTransactionManagement//启用了事务管理的功能 public class SpringConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { //DataSourceTransactionManager是PlatformTransactionManager的一个实现类 //它专门用于管理基于DataSource的数据源事务,通过构造函数注入DataSource,与具体的数据库连接关联 return new DataSourceTransactionManager(dataSource); } @Bean public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { return new TransactionTemplate(transactionManager); } } \~\~\~ \~\~\~java @Autowired private TransactionTemplate transactionTemplate; @Override public void testTr() { //TransactionCallbackWithoutResult:代表无返回值 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // 执行业务逻辑 //添加用户 User u=new User();u.setUsername("woniu6"); userDao.save(u); //模拟异常 int a=1/0; //添加书籍 Book b=new Book();b.setBookName("woniu6"); bookDao.save(b); // 无需手动调用提交,正常执行完自动提交, // 无需手动调用回滚,如果异常会自动回滚 // 如果需要手动回滚,可以调用: status.setRollbackOnly(); } }); \~\~\~