整合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 {}