整合MyBatis
分析
最终需要管理的是Mapper实例
Mapper实例没有实现类,是通过SqlSession获得,但Sqlsession是线程不安全的,不能直接管理SqlSession,因为每个线程的SqlSession都是不一样的。
所以只能统一管理线程安全的SqlSessionFactory,通过他来获取SqlSsession,然后管理
mybatis对Spring支持的依赖中提供了注册SqlSessionFactory组件的工厂类
引入依赖
mybatis-spring、spring-jdbc、spring-tx
spring-xxx是官方的依赖,xxx-spring是第三方
<!--整合mybatis对Spring的支持--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!--spring-tx通过jdbc的引入可以一起引入进来--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.15.RELEASE</version> </dependency>
注册SqlSessionFactory组件
提供了一个工厂bean → FactoryBean
SqlSessionFactoryBean
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>{ public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { this.afterPropertiesSet(); } return this.sqlSessionFactory; } public Class<? extends SqlSessionFactory> getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); } }
这个是已经提供好的,我们直接注册即可,不需要重新写
最终的返回值就是一个SqlSessionFactory
Mapper实例
找到所有的Mapper接口,将接口对应的代理实例注册为容器中的组件
我们需要提供的就是接口的位置
MapperScannerConfigurer 进行Mapper接口包目录管理的配置
注册DataSource组件需要引入druid依赖
<!--注册DataSource组件--> <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/practice?useSSL=false&characterEncoding=utf8&allowMultiQueries=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--可以提供datasource组件--> <property name="dataSource" ref="datasource"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--告知接口的位置--> <property name="basePackage" value="com.cskaoyan.mapper"/> <!--告知SqlSessionFactory--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>
要注意,在导入以上这些依赖的同时,也需要导入mybatis本身!
同时也要注意url属性里面要配置合适的连接,比如编码格式等等
Spring事务
为什么要管理事务?
MyBatis整合完Spring之后,已经由Spring自动管理事务。每执行完一行Mapper方法,都会提交事务。
有一些情况需要将多行的执行作为一个事务,这时候就需要Spring做进一步的事务管理
事务
A原子性C一致性I隔离性D持久性
事务并发引起的问题:脏读、不可重复读、虚幻读
数据库隔离级别:读未提交、读已提交、可重复读、串行化
脏读 | 不可重复读 | 虚幻读 | |
读未提交 | × | × | × |
读已提交 | √ | × | × |
可重复读 | √ | √ | × |
串行化 | √ | √ | √ |
mysql默认的隔离级别:可重复读,并可以解决虚幻读的问题
原理
代理
所做的增强 → 事务
BeanPostProcessor
核心接口
Spring管理事务就是使用这些核心接口中提供的方法。
代理
PlatformTransactionManager
平台事务管理器
提供了三个方法
由于平台事务管理器是一个接口,所以我们在使用的时候一般是使用实现类
DataSourceTransactionManager
public interface PlatformTransactionManager extends TransactionManager { //根据definition获得status TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; //根据status做提交或回滚 void commit(TransactionStatus var1) throws TransactionException; void rollback(TransactionStatus var1) throws TransactionException; }
TransactionStatus是一个过程值,而面向程序员的是TransactionDefinition
TransactionStatus
事务状态
TransactionDefinition
事务的定义
事务的名称、只读属性、隔离级别、超时时间、传播行为
PropagationBehavior传播行为
多个方法之间如何来共享事务 → 多个方法都增加上了事务,方法之间存在着调用关系,如果发生异常,存在着调用关系的方法哪一些回滚,哪一些提交
比如现在有一个method1,method1调用了method2,如果method1发生了异常,谁提交谁回滚;如果method2发生了异常谁提交谁回滚。
默认传播行为REQUIRED
如果没有事务,则新增一个;如果有事务,则加入进来,作为一个事务。
同生共死,要么一起提交,要么一起回滚
比如现在有一个method1,method1调用了method2,
如果method1发生了异常,谁提交谁回滚? 都回滚
如果method2发生了异常,谁提交谁回滚? 都回滚
REQURIES_NEW
如果没有事务,则新增一个;如果有事务,则新增一个新事务。
自私型。外围不能影响内部,但是内部可以响应外围。
比如现在有一个method1,method1调用了method2,
如果method1发生了异常,谁提交谁回滚? 1回滚2提交
如果method2发生了异常,谁提交谁回滚? 都回滚
NESTED
如果没有事务,则新增一个;如果有事务,则以嵌套事务的方式运行。
无私型。外围会响应内部,但是内部不会影响外围。
PDD 砍一刀,拉新客,获得新用户的成本。
注册 → 发放新用户优惠券
注册相对于发放优惠券更重要,而注册是外围方法,保外围方法 → nested
Definition接口
public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; int TIMEOUT_DEFAULT = -1; default int getPropagationBehavior() { return 0; } default int getIsolationLevel() { return -1; } default int getTimeout() { return -1; } default boolean isReadOnly() { return false; } @Nullable default String getName() { return null; } static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; } }
案例
Spring要管理事务,容器中要管理PlatformTransactionManager组件
这个时候可以用Spring提供好的组件
<!--PlatformTransactionManager组件--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"/> </bean>
手动编程
首先要添加一个管理事务的组件
<!--手动增加事务 → TransactionTemplate--> <bean class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean>
相当于实现了一个接口
@FunctionalInterface public interface TransactionCallback<T> { @Nullable T doInTransaction(TransactionStatus var1); }
在这个接口之中,我们做业务的操作,即将事务都写在接口里面,这样就能达到一体的效果
@Service public class AccountServiceImpl implements AccountService { @Autowired AccountMapper mapper; //从容器中取出mapper @Autowired TransactionTemplate txTemplate; //取出手动增加事务的组件 @Override public void exchangeMoney(String fromName, String toName, Integer money) { int fromMoney = mapper.selectMoneyByName(fromName); int toMoney = mapper.selectMoneyByName(toName); fromMoney -= money; toMoney +=money; int finalToMoney = toMoney; int finalFromMoney = fromMoney; Object execute = txTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus transactionStatus) { mapper.updateAccount(fromName, finalFromMoney); // System.out.println(1/0); mapper.updateAccount(toName, finalToMoney); return "doInTransaction"; //这里的返回值最终给到了execute } }); System.out.println(execute); } }
每一处出现事务的地方,都需要手动编程增加上TransactionTemplate的方法
代理对象
生成一个代理对象 → 增强是事务
TransactionProxyFactoryBean
委托类组件是谁、TransactionManager、TransactionDefinition
通知不需要
spring提供好的动态代理对象,需要在容器中注册
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="accountServiceImpl"/> <property name="transactionManager" ref="transactionManager"/> <!--事务参数 → Definition--> <property name="transactionAttributes"> <props> <!--key:方法名--> <!--value:definition--> <!-- ISOLATION_XXX 隔离级别 PROPAGATION_XXX 传播行为 timeout_数字 超时时间,单位是秒 readOnly 只读 +Exception noRollBack -Exception rollBack --> <prop key="transfer">ISOLATION_DEFAULT,PROPAGATION_REQUIRES_NEW,timeout_5</prop> </props> </property> </bean>
aspectj → advisor(可用)
pointcut和advice
<aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.cskaoyan.service..*(..))"/> <!--advisor的通知 implements MethodInterceptor--> <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/> </aop:config> <!--spring给我们提供了一种直接配置TransactionInterceptor的写法 → tx:advice--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--对方法增加上个性化的definition:方法和definition的对应关系--> <tx:attributes> <!--name属性:方法名,可以使用通配符*--> <tx:method name="transfer" read-only="true"/> <!--<tx:method name="transfer2"/>--> </tx:attributes> </tx:advice>
***声明式事务
事务注解加在哪个方法上,哪个方法就被增强了 → 事务
注解 @Transactional
注解和方法绑定起来,Definition也是和注解绑定起来 → 注解的属性
注解加在类上就表示该类的所有方法都增加事务
//可以写在类上,也可以写在方法上 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
打开注解开关
<!--PlatformTransactionManager组件--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW,noRollbackFor = ArithmeticException.class) @Override public void transfer(Integer fromId, Integer destId, Integer money) { //原始money Integer fromMoney = accountMapper.selectMoneyById(fromId); Integer destMoney = accountMapper.selectMoneyById(destId); //计算转账后的money fromMoney -= money; destMoney += money; //执行更新,更新转账后的money accountMapper.update(fromId, fromMoney); //事务提交了 int i = 1 / 0; //发生异常 try { Thread.sleep(1000); //Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } accountMapper.update(destId, destMoney); //没有执行到 }
JavaConfig
以Java代码和注解的方式来替代xml配置的 → 铺垫、SpringBoot推荐使用的配置方式
我们是xml标签做了哪些事情
- 组件注册
- 扫描包配置context:component-scan base-package √
- 引入properties配置文件 context:property-placeholder location √
- 打开aspectj的注解开关 aop:aspectj-autoproxy √
- 事务的注解驱动 tx:annotation-driven √
配置类 @Configuration
@Configuration
把当前类作为配置类,同时也是容器中的组件
扫描包配置
@ComponentScan(“包目录”)
写在配置类上
@Configuration @ComponentScan("com.cskaoyan") public class SpringConfig { }
properties配置文件
@Configuration @ComponentScan("com.cskaoyan") //@PropertySource("classpath:param.properties") public class SpringConfig { }
配置类加载不到properties文件的值
aspectj注解开关
@Configuration @ComponentScan("com.cskaoyan") //@PropertySource("classpath:param.properties") @EnableAspectJAutoProxy //要引入aspectjweaver依赖 public class SpringConfig { }
事务注解驱动
@Configuration @ComponentScan("com.cskaoyan") //@PropertySource("classpath:param.properties") @EnableAspectJAutoProxy //要引入aspectjweaver依赖 @EnableTransactionManagement //容器中要有TransactionManager组件 public class SpringConfig { }
***组件注册
组件注册以方法的形式存在,方法的返回值作为组件注册到容器中,默认的id就是方法名,也可以自定义
@Configuration //标记为配置类 @ComponentScan("com.fh") //扫描包 @EnableAspectJAutoProxy //aspectj注解开关 @EnableTransactionManagement //事务注解驱动,打开之后一定要注册TransactionManager组件 public class SpringConfig { //组件id:默认值 👉 方法名 // 设定值 👉 @Bean注解的value属性值 //@Bean // id = dataSource @Bean public DruidDataSource dataSource(){ //引入数据源 DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/practice?useSSL=false&characterEncoding=utf8&allowMultiQueries=true"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } //形参:从容器中按照类型取出组件(该类型组件在容器中只有一个),如果该类型组件不止一个,需要指定组件id @Qualifier //维护一个sqlSessionFactory @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dataSource")DataSource dataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ //扫描包配置 MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.fh.mapper"); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean"); return mapperScannerConfigurer; } @Bean public DataSourceTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
测试类
@RunWith(SpringJUnit4ClassRunner.class) //配置文件加载的是配置类 @ContextConfiguration(classes = SpringConfig.class) public class MyTest {}