springboot整合Mybatis、事务、多数据源
一. 整合Mybatis
第一步:导入包
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
第二步:如果需要使用mapper配置文件的,可以在application.properties中添加配置
mybatis.mapper-locations=classpath:mapper/*.xml
第三步:在配置类中添加配置
@MapperScan("com.qianfeng.demo.dao") // 扫描mybatis的接口类
@SpringBootApplication
public class Demo31Application {
public static void main(String[] args) {
SpringApplication.run(Demo31Application.class, args);
}
}
第四步:编写接口并编写注解配置(或编写对应的Mapper.xml)
@Repository
public interface StudentDAO {
@Select({"SELECT id, name, sex from student_info"})
public List<Student> findAll();
}
二. 事务
2.1 回顾事务的特征
2.1.1 什么是事务(Transaction)
是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql 能将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性。事务通常是以begin transaction开始,以commit或rollback结束。Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
1)更新客户所购商品的库存信息
2)保存客户付款信息–可能包括与银行系统的交互
3)生成订单并且保存到数据库中
4)更新用户相关信息,例如购物数量等等
正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态–库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。
数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术
2.1.2 为什么要使用事务?
- 为了提高性能
- 为了保持业务流程的完整性
2.1.3 事务的特性
ACID
- 原子性(atomicity)
事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
- 一致性(consistency)
事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
- 隔离性(isolation)
一个事务的执行不能被其他事务所影响。企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。由数据库理论可知,由于并发访问,在不可预料的时刻可能引发如下几个可以预料的问题:
- 持久性(durability)
一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改
2.1.4 事务的并发问题
- 脏读(Dirty Read)
一个事务读取到了另一个事务未提交的数据操作结果。这是相当危险的,因为很可能所有的操作都被回滚。
- 不可重复读(虚读)(NonRepeatable Read)
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
- 幻读(Phantom Read)
事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据,这是因为在两次查询过程中有另外一个事务插入数据造成的
2.1.5 事务的隔离级别
- 1- 读未提交
Read uncommitted:最低级别,以上情况均无法保证。
- 2- 读已提交
Read committed:可避免脏读情况发生。(Oracle默认)
- 4- 可重复读
Repeatable read:可避免脏读、不可重复读情况的发生。不可以避免虚读。(MySQl默认)
- 8- 串行化读
Serializable:事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重.
2.1.6 事务的传播行为
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
注意:Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。这是Spring为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。
Spring中七种事务传播行为:
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
2.2 spring整合MyBatis的事务配置(声明式事务)
2.2.1 使用配置文件方式:applicationContext.xml
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<!-- 配置通知(配置事务执行的方法定义的名称规则) -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置AOP的规则 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pc" expression="execution(* com.qianfeng.demo.service.*.*(..))" />
<aop:advisor pointcut-ref="pc" advice-ref="myAdvice" />
</aop:config>
注意:使用此配置要求service中的方法名称必须按照规则编写。
2.2.2 使用注解的方式
第一步:在配置文件中添加:
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
第二步:在类中添加注解即可:
@Service
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class CustomerService {
@Resource
private CustomerDAO customerDAO;
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<Customer> findAll(){
return customerDAO.findAll();
}
}
2.3 在springboot中使用事务
springboot由于可以自动配置,所以几乎不需要任何配置就可以使用注解的方式实现事务。
@Service
public class CustomerService {
@Resource
private CustomerDAO customerDAO;
public List<Customer> findAll(){
return customerDAO.findAll();
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void transferMoney(Integer fromId, Integer toId, Double money) throws Exception {
customerDAO.increaseMoney(toId, money);
Customer customer = customerDAO.findById(fromId);
if (customer != null && customer.getMoney() >= money) {
customerDAO.decreaseMoney(fromId, money);
}else {
throw new Exception("余额不足");
}
}
}
三. 在springboot中整合mybatis使用多数据源
3.1 多数据源的配置
springboot中可以使用多个数据源,但是需要对多个数据源进行配置,最简单的办法就是分别配置:
第一步:在application.properties
中配置各个数据源的参数
#第一个数据源
spring.datasource.bank1.username=root
spring.datasource.bank1.password=root
spring.datasource.bank1.url=jdbc:mysql://localhost:3306/bank1?useUnicode=true&characterEncoding=utf-8
spring.datasource.bank1.driver-class-name=com.mysql.jdbc.Driver
bank1.mybatis.mapper-locations=classpath:mapper/bank1/*.xml
#第二个数据源
spring.datasource.bank2.username=root
spring.datasource.bank2.password=root
spring.datasource.bank2.url=jdbc:mysql://localhost:3306/bank2?useUnicode=true&characterEncoding=utf-8
spring.datasource.bank2.driver-class-name=com.mysql.jdbc.Driver
bank2.mybatis.mapper-locations=classpath:mapper/bank2/*.xml
第二步:编写对应的配置类:(第一个数据源的配置)
@Configuration
// 扫描的包路径
@MapperScan(basePackages = "com.qianfeng.demo.dao.bank1",
sqlSessionTemplateRef = "bank1SqlSessionTemplate") // 引用的template的name
public class Bank1DataSourceConfig {
// 引用application.properties中的路径
@Value("${bank1.mybatis.mapper-locations}")
private String mapperLocation;
// 配置dataSource
@Bean(name = "bank1DataSource")
@Primary // 配置为默认值
@ConfigurationProperties(prefix = "spring.datasource.bank1") // application.properties中数据源的前缀
public DataSource bank1DataSource(){
return DataSourceBuilder.create().build();
}
@Primary // 配置为默认值
@Bean(name = "bank1SqlSessionFactory")
public SqlSessionFactory bank1SqlSessionFactory(@Qualifier("bank1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources(mapperLocation));
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "bank1TransactionManager")
@Primary // 配置为默认值
public DataSourceTransactionManager bank1TransactionManager(@Qualifier("bank1DataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "bank1SqlSessionTemplate")
@Primary // 配置为默认值
public SqlSessionTemplate bank1SqlSessionTemplate(@Qualifier("bank1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
第二个数据源的配置:
注意:几乎和第一个数据源的配置几乎一样,但是一定不要配置@Primary注解,因为不可能有两个默认值
@Configuration
@MapperScan(basePackages = "com.qianfeng.demo.dao.bank2",
sqlSessionTemplateRef = "bank2SqlSessionTemplate")
public class Bank2DataSourceConfig {
@Value("${bank2.mybatis.mapper-locations}")
private String mapperLocation;
@Bean(name = "bank2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.bank2")
public DataSource bank2DataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "bank2SqlSessionFactory")
public SqlSessionFactory bank2SqlSessionFactory(@Qualifier("bank2DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources(mapperLocation));
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "bank2TransactionManager")
public DataSourceTransactionManager bank2TransactionManager(@Qualifier("bank2DataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "bank2SqlSessionTemplate")
public SqlSessionTemplate bank2SqlSessionTemplate(@Qualifier("bank2SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
注意:DAO中名称的唯一性,避免系统自动装配的时候找不到唯一的名称。
3.2 多数据源的事务管理
由于多数据源中的数据是跨数据库的,单数据库那种事务管理是无效的,这里需要使用JTA去完成(这里使用atomikos)。
第一步:导入atomikos的包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
第二步:在application.properties
中编写atomikos的多数据源的配置(这里只是简单配置,更多参数配置请参考对应的文档)
spring.jta.enabled=true
spring.datasource.bank1.xa-properties.url=jdbc:mysql://localhost:3306/bank1?useUnicode=true&characterEncoding=utf-8
spring.datasource.bank1.xa-properties.user=root
spring.datasource.bank1.xa-properties.password=root
spring.datasource.bank1.xa-data-source-class-name=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
spring.datasource.bank1.unique-resource-name=bank1DataSource
bank1.mybatis.mapper-locations=classpath:mapper/bank1/*.xml
spring.datasource.bank2.xa-properties.url=jdbc:mysql://localhost:3306/bank2?useUnicode=true&characterEncoding=utf-8
spring.datasource.bank2.xa-properties.user=root
spring.datasource.bank2.xa-properties.password=root
spring.datasource.bank2.xa-data-source-class-name=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
spring.datasource.bank2.unique-resource-name=bank2DataSource
bank2.mybatis.mapper-locations=classpath:mapper/bank2/*.xml
第三步:修改配置类中的代码,将原来的事务管理的代码注释掉,并将数据源换成atomikos的数据源。
@Configuration
@MapperScan(basePackages = "com.qianfeng.demo.dao.bank1",
sqlSessionTemplateRef = "bank1SqlSessionTemplate")
public class Bank1DataSourceConfig {
@Value("${bank1.mybatis.mapper-locations}")
private String mapperLocation;
@Bean(name = "bank1DataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.bank1")
public DataSource bank1DataSource(){
// return DataSourceBuilder.create().build();
return new AtomikosDataSourceBean();
}
@Primary
@Bean(name = "bank1SqlSessionFactory")
public SqlSessionFactory bank1SqlSessionFactory(@Qualifier("bank1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources(mapperLocation));
return sqlSessionFactoryBean.getObject();
}
// @Bean(name = "bank1TransactionManager")
// @Primary
// public DataSourceTransactionManager bank1TransactionManager(@Qualifier("bank1DataSource") DataSource dataSource){
// return new DataSourceTransactionManager(dataSource);
// }
@Bean(name = "bank1SqlSessionTemplate")
@Primary
public SqlSessionTemplate bank1SqlSessionTemplate(@Qualifier("bank1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}