Spring Boot学习笔记04--数据访问

摘要

看完本文你将掌握如下知识点:

  1. Spring Boot对JDBC的支持
  2. Spring Boot项目多数据源的配置
  3. Spring Boot的事务管理
  4. Spring Boot项目多数据源的事务管理
  5. Spring Boot项目中使用Hibernate4的方法
  6. Spring Boot项目中使用Mybatis的方法

SpringBoot系列Spring Boot学习笔记


前言

Spring Boot针对企业开发场景提供了各种『开箱即用』的spring-boot-starter-xxx自动配置依赖模块,这就使得我们开发Spring应用更加快速和高效。比如我们前面创建web项目时使用到的spring-boot-starter-web

这些spring-boot-starter-xxx不但包含了对该功能的全部依赖包,同时也提供了该功能的自动配置类。我们本节要讨论的『数据访问』就是基于这些spring-boot-starter-xxx的自动配置依赖模块。


环境准备

jdk版本:java version “1.8.0_31”
数据库:10.1.16-MariaDB
脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 创建库1
CREATE SCHEMA `springboot1` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot1`.`person` (
`p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`p_name` VARCHAR(45) NULL COMMENT '姓名',
`p_age` INT NULL COMMENT '年龄',
PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人员信息表';

INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '张三', '20');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');


# 创建库2
CREATE SCHEMA `springboot2` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot2`.`person` (
`p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`p_name` VARCHAR(45) NULL COMMENT '姓名',
`p_age` INT NULL COMMENT '年龄',
PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人员信息表';

INSERT INTO `springboot2`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '张三', '20');
INSERT INTO `springboot2`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');

Spring Boot对JDBC的支持

创建项目

新建一个springboot项目,依赖选择web和jdbc

项目创建成功后查看pom,会看到添加了spring-boot-starter-jdbc的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

配置项目

在pom中增加MySQL依赖

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>

application.properties中添加数据源配置信息

1
2
3
4
5
#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd

项目代码

本例只做简单演示,所以只创建如下3个类,并用一个单元测试类进行测试
Model:Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person implements Serializable {
private static final long serialVersionUID = -1L;
private Long id;
private String name;
private Integer age;

//getter and setter

@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

Dao:PersonDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Repository
public class PersonDao {

@Autowired
private JdbcTemplate jdbcTemplate;

public int savePerson(Person person){
String sql = "INSERT INTO `springboot1`.`person` (`p_name`, `p_age`) VALUES (?, ?)";
int result = jdbcTemplate.update(sql,new Object[]{person.getName(),person.getAge()});
return result;
}

public List<Person> getAllPersonList(){
String sql = "select * from person s";
List<Person> list = jdbcTemplate.query(sql,new PersonMapper());
return list;
}

class PersonMapper implements RowMapper<Person>{
@Override
public Person mapRow(ResultSet resultSet, int i) throws SQLException {
Person person = new Person();
person.setId(resultSet.getLong("p_id"));
person.setName(resultSet.getString("p_name"));
person.setAge(resultSet.getInt("p_age"));
return person;
}
}
}

Service:PersonService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class PersonService {

@Autowired
private PersonDao personDao;

public int savePserson(Person person){
return personDao.savePerson(person);

}
public List<Person> getAllPersonList(){
return personDao.getAllPersonList();
}
}

单元测试:SpringbootjdbcdemoApplicationTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootjdbcdemoApplicationTests {

@Autowired
private PersonService personService;

@Test
public void savePerson(){
Person person = new Person();
person.setName("王五");
person.setAge(18);
int result = personService.savePserson(person);
Assert.assertEquals(1,result);
}

@Test
public void getAllPersonList(){
List<Person> list = personService.getAllPersonList();
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
}

说明
实际上,项目加入spring-boot-starter-jdbc的依赖后,即可在项目代码中通过@Autowired自动注入JdbcTemplate。而数据源的配置则在application.properties中进行配置。

如果不想使用spring-boot-starter-jdbc带来的默认依赖和自动配置,那么采用如下的方式,效果是一样的。


使用自定义的DataSourceConfig

修改pom中的依赖,去掉对spring-boot-starter-jdbc的依赖,并加入对spring-jdbc的依赖,这样我们就失去了对JDBC的自动配置功能了。

1
2
3
4
5
6
7
8
9
10
11
12
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
-->

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>

启动类中去掉对DataSourceAutoConfiguration的自动配置支持

1
2
3
4
5
6
7
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class SpringbootjdbcdemoApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootjdbcdemoApplication.class, args);
}
}

创建DataSourceConfig配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class DataSourceConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;

@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}

@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
}

其它代码不需要任何修改,运行效果一致。


说明
为什么SpringBoot为我们提供了spring-boot-starter-jdbc的自动配置解决方案,我们还要自己配置呢,这是因为自动配置并不是那么的强大,spring-boot-starter-jdbc只能支持单一的数据源配置,如果项目中需要关联多个数据源,就需要我们自己处理了。

比如我们在环境准备中创建了两个数据库,接下来我们在项目中增加多数据源的配置。


application.properties中添加数据源配置信息

1
2
3
4
5
#datasource2
spring.datasource.driver-class-name2=com.mysql.jdbc.Driver
spring.datasource.url2=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf-8
spring.datasource.username2=root
spring.datasource.password2=newpwd

然后在DataSourceConfig配置类中增加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;

@Bean(name = "dataSource2")
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass2);
dataSource.setUrl(url2);
dataSource.setUsername(userName2);
dataSource.setPassword(passWord2);
return dataSource;
}

@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
return jdbcTemplate;
}

此时需要在Dao中将@Autowired注解替换成@Resource(name = "jdbcTemplate"),来明确指定要使用哪一个jdbcTemplate对象。


说明
关于如何在项目中使用Hibernate4框架,可以参考:SpringMVC4零配置


Spring Boot的事务管理

JDBC事务管理

如果我们项目中使用的是JDBC的数据访问方案,并且容器中只注册了一个DataSource,那么SpringBoot就会为我们开启DataSourceTransactionManagerAutoConfiguration的自动配置类,其会在容器中注册一个DataSourceTransactionManager事务管理器,同时会开启对注解式事务**@Transactional的支持。感兴趣的可以看一下DataSourceTransactionManagerAutoConfiguration**的源码。


@Transactional是Spring框架提供的,配置方法参考下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//一般我们会在业务实现类上声明事务注解
//当前表示需要在事务中运行,可以执行更新和删除操作,遇到异常则回滚
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService{
//方法上也可以标注事务注解,方法上注解声明会覆盖类上的
//一般查询操作readOnly设置为true,增删该操作设置为false
@Transactional(readOnly = true)
public List<Person> getAllPersonList(){
//do something
}

//不加@Transactiona注解,则使用类上的设置
public int savePserson(Person person){
//do something
}
}

如果在测试类上声明**@Transactional**,则会开启自动回滚,不会产生脏数据

1
2
3
4
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class SpringbootjdbcdemoApplicationTests {…………}

如果希望自己配置事务,可以在配置类中增加事务管理器的配置,比如,我们在DataSourceConfig中增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
//启用注解事务管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;

@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}

@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}

@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}


说明
上面的方法只是针对单一数据源进行事务管理的,但是项目中经常会用到多数据源的情况,那么要如何进行事务管理呢?


我们上文讲到了可以在项目中通过配置类,自己配置多个数据源,并通过DataSourceConfig进行了演示,接下来我们添加多个事务管理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Configuration
//启用注解事务管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;


@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;

@Bean(name = "dataSource")
public DataSource dataSource() {

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}

@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}

@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}

@Bean(name = "dataSource2")
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass2);
dataSource.setUrl(url2);
dataSource.setUsername(userName2);
dataSource.setPassword(passWord2);
System.out.println(url2);
return dataSource;
}

@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
return jdbcTemplate;
}

@Bean(name = "transactionManager2")
public DataSourceTransactionManager transactionManager2() {
return new DataSourceTransactionManager(dataSource2());
}
}

这时,我们必须在**@Transactional**注解中指定要使用哪一个事务管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService {

@Autowired
private PersonDao personDao;

public int savePserson(Person person){
return personDao.savePerson(person);
}
@Transactional(transactionManager = "transactionManager",readOnly = true)
public List<Person> getAllPersonList(){
return personDao.getAllPersonList();
}

@Transactional(transactionManager = "transactionManager2",propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public int savePserson2(Person person){
return personDao.savePerson2(person);
}
@Transactional(transactionManager = "transactionManager2",readOnly = true)
public List<Person> getAllPersonList2(){
return personDao.getAllPersonList2();
}
}


说明
这样做并不美好,不能对多个数据源同时进行事务管理,比如,我们在一个业务方法里同时对两个数据源进行操作,我们希望只要有一个发生异常,则两个数据源的数据都进行回滚。

那要怎么做呢,我们接着往下看。


多数据源事务管理

这里推荐使用**Atomikos**,Atomikos支持Mysql、Oracle等多种数据库,可与多种ORM框架集成,如MyBatis、JPA、Hibernate等等,同时支持各种容器下JNDI的多数据源管理。Atomikos官网提供了各种情况下使用Atomikos的Example,本文只对使用JDBC时的情况进行说明。

目前maven中央仓库的最新版本是4.0.4,使用Atomikos,需要在项目中加入如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

DataSourceConfig进行改造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package com.example;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Configuration
//启用注解事务管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;


@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;


@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(300);
return userTransactionImp;
}

@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}

@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}

@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println("dataSource init");
//Oracle:oracle.jdbc.xa.client.OracleXADataSource
//Druid:com.alibaba.druid.pool.xa.DruidXADataSource
//Postgresql:org.postgresql.xa.PGXADataSource
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}

@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() {
System.out.println("dataSource2 init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url2);
mysqlXaDataSource.setPassword(passWord2);
mysqlXaDataSource.setUser(userName2);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}

@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}


@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
return jdbcTemplate;
}
}

项目编译路径下可以创建一个jta.properties文件,用于对Atomikos的相关属性进行配置,不过也可以不加这个文件,因为所有的属性都有默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
com.atomikos.icatch.enable_logging=true
com.atomikos.icatch.force_shutdown_on_vm_exit=false
com.atomikos.icatch.automatic_resource_registration=true
com.atomikos.icatch.checkpoint_interval=500
com.atomikos.icatch.serial_jta_transactions=true
com.atomikos.icatch.default_jta_timeout=10000
com.atomikos.icatch.max_timeout=300000
com.atomikos.icatch.log_base_dir=./
com.atomikos.icatch.threaded_2pc=false
com.atomikos.icatch.max_actives=50
com.atomikos.icatch.log_base_name=tmlog
java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory
com.atomikos.icatch.client_demarcation=false
java.naming.provider.url=rmi://localhost:1099
com.atomikos.icatch.rmi_export_class=none
com.atomikos.icatch.trust_client_tm=false
com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}
com.atomikos.icatch.oltp_max_retries=5
com.atomikos.icatch.oltp_retry_interval=10000
com.atomikos.icatch.allow_subtransactions=true

Spring Boot中Atomikos与Hibernate4多数据源集成方法

Atomikos与Hibernate4集成方法与JDBC类似,我们在pom中加入hibernate的依赖,并对DataSourceConfig进行改造
pom

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>

DataSourceConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package com.example;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.example.hibernate.CP_HibernateDAO;
import com.example.hibernate.impl.CP_Hibernate4DAOImpl;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import java.util.Properties;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;


@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;


@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}

@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}

@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
System.out.println();
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);

return jtaTransactionManager;
}

@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println();

MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);

return xaDataSource;
}

@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() {
System.out.println();
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url2);
mysqlXaDataSource.setPassword(passWord2);
mysqlXaDataSource.setUser(userName2);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}


@Bean(name = "sessionFactory")
public LocalSessionFactoryBean localSessionFactoryBean() {
System.out.println("sessionFactory");
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
//扫描实体对象的目录,不同的数据源,实体要存放不同的目录
String[] packagesToScan = new String[] { "com.example.model.ds1" };
sessionFactory.setPackagesToScan(packagesToScan);

Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
hibernateProperties.setProperty("hibernate.show_sql", "true");

//开启Hibernate对JTA的支持
hibernateProperties.setProperty("hibernate.current_session_context_class", "jta");
hibernateProperties.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");

sessionFactory.setHibernateProperties(hibernateProperties);

return sessionFactory;

}

@Bean(name = "sessionFactory2")
public LocalSessionFactoryBean localSessionFactoryBean2() {
System.out.println("sessionFactory2");
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource2());
//扫描实体对象的目录,不同的数据源,实体要存放不同的目录
String[] packagesToScan = new String[] { "com.example.model.ds2" };
sessionFactory.setPackagesToScan(packagesToScan);

Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
hibernateProperties.setProperty("hibernate.show_sql", "true");

//开启Hibernate对JTA的支持
hibernateProperties.setProperty("hibernate.current_session_context_class", "jta");
hibernateProperties.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");

sessionFactory.setHibernateProperties(hibernateProperties);

return sessionFactory;

}

@Bean(name = "hibernateDAO")
public CP_HibernateDAO hibernate4Dao() {
System.out.println("hibernateDAO");
CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl();
//绑定SessionFactory
dao.setSessionFactory(localSessionFactoryBean().getObject());
return dao;
}

@Bean(name = "hibernateDAO2")
public CP_HibernateDAO hibernate4Dao2() {
System.out.println("hibernateDAO2");
CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl();
//绑定SessionFactory2
dao.setSessionFactory(localSessionFactoryBean2().getObject());
return dao;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "person")
public class Person implements Serializable {
private static final long serialVersionUID = -1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "p_id")
private Long id;
@Column(name = "p_name")
private String name;
@Column(name = "p_age")
private Integer age;
//setter and getter
}

CP_HibernateDAO是我们自定义的Hibernate的通用Dao接口,其定义的方法和和实现类CP_Hibernate4DAOImpl代码如下:

1
2
3
4
5
6
7
8
9
10
11
package com.example.hibernate;

import java.util.List;

public interface CP_HibernateDAO {

public List<?> findAll(Class<?> entityClazz, String... str);

public void save(Object entity);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.example.hibernate.impl;

import com.example.hibernate.CP_HibernateDAO;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;

import java.util.List;

public class CP_Hibernate4DAOImpl implements CP_HibernateDAO {


private SessionFactory sessionFactory;

public SessionFactory getSessionFactory() {
return sessionFactory;
}
//绑定SessionFactory
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}


private Session getHibernateSession() {
Session session = sessionFactory.openSession();
return session;
}


/*
* @see com.example.hibernate.CP_HibernateDAO#findAll()
*/
@Override
public List<?> findAll(Class<?> entityClazz, String... str) {
DetachedCriteria dc = DetachedCriteria.forClass(entityClazz);
List<?> list = findAllByCriteria(dc);
return list;
}


/*
* @see com.example.hibernate.CP_HibernateDAO#save(java.lang.Object)
*/
@Override
public void save(Object entity) {

getHibernateSession().save(entity);
//注意这里一定要执行flush方法
getHibernateSession().flush();
}


public List<?> findAllByCriteria(DetachedCriteria detachedCriteria) {
// TODO Auto-generated method stub
Criteria criteria = detachedCriteria
.getExecutableCriteria(getHibernateSession());
return criteria.list();
}

}

说明
需要注意两点:

  1. session必须使用sessionFactory.openSession()的方式获得,不能使用sessionFactory.getCurrentSession()。

  2. 更新操作必须调用session.flush()方法。

Spring配置文件的方式,可以参考:Spring4+Hibernate4+Atomikos3.3多数据源事务管理


Spring Boot中Mybitas的使用

创建项目时,我们可以选择mybatis-spring-boot-starter依赖,这样可以激活SpringBoot对Mybatis的自动配置类。

pom中添加依赖

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>

application.properties中添加mybaits的自动配置属性,可以查看MybatisProperties了解可以配置哪些属性

1
2
#mapper配置文件路径,如果是基于注解的形式可以不需要配置该属性
mybatis.mapper-locations=classpath:mapper/*.xml

Mapper接口上要配置**@Mapper注解,因为mybatis-spring-boot-starter的自动配置会扫描@Mapper**注解来注册Mapper接口。

1
2
3
4
@Mapper
public interface PersonMapper {
//………………
}

此时同样可以使用**@Transactional**注解


说明
可以使用maven的mybatis-generator插件自动生成代码,参考maven插件–MyBatis自动生成代码


mybatis-spring-boot-starter不利于扩展,所以还是我们自己实现个mybitas的配置类吧。

pom中去掉mybatis-spring-boot-starter的依赖,增加mybatis的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>-->

<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>

创建MyBatisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;

@Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}

@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource());

//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

@Bean
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}

MyBatisMapperScannerConfig,基于包扫描Mapper,此时不需要配置**@Mapper**注解

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
//必须在MyBatisConfig注册后再加载MapperScannerConfigurer,否则会报错
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("com.example.mapper");
return mapperScannerConfigurer;
}
}

关闭DataSourceAutoConfiguration,因为这里我们配置了数据源,所以需要关闭该自动配置,另外,MybatisAutoConfiguration也是基于DataSourceAutoConfiguration的,所以关闭了DataSourceAutoConfiguration也就同时关闭了MybatisAutoConfiguration


Spring Boot中Atomikos与Mybatis多数据源集成方法

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>

<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>

<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>4.0.4</version>
</dependency>

<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>

MyBatisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;



@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;


@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}

@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}

@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}

@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println("dataSource init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);

return xaDataSource;
}

@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource2() {
System.out.println("dataSource2 init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url2);
mysqlXaDataSource.setPassword(passWord2);
mysqlXaDataSource.setUser(userName2);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);
return xaDataSource;
}

//基于xml式Mapper
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource());

//添加Mapper配置文件的目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:mapper/ds1/*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate() {
return new SqlSessionTemplate(sqlSessionFactoryBean());
}

//基于注解式Mapper
@Bean(name = "sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactoryBean2() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource2());
try {
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Bean(name = "sqlSessionTemplate2")
public SqlSessionTemplate sqlSessionTemplate2() {
return new SqlSessionTemplate(sqlSessionFactoryBean2());
}
}

MyBatisMapperScannerConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
//必须在MyBatisConfig注册后再加载MapperScannerConfigurer,否则会报错
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//绑定datasorce的sqlSessionFactory
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
//扫描ds1目录来注册Mapper接口
mapperScannerConfigurer.setBasePackage("com.example.mapper.ds1");
return mapperScannerConfigurer;
}

@Bean
public MapperScannerConfigurer mapperScannerConfigurer2() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//绑定datasorce2的sqlSessionFactory
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory2");
//扫描ds2目录来注册Mapper接口
mapperScannerConfigurer.setBasePackage("com.example.mapper.ds2");
return mapperScannerConfigurer;
}
}

这里要说明的是,如果两个数据源下的Mapper起了相同的类名,虽然他们在不同的包路径下,启动也会报错了,因为默认注册Mapper时使用的是类名称(不含包名),此时可以在Mapper上加上**@Component(“personMapper”)**注解


写在后面的话

Spring Boot为我们提供了大量的spring-boot-starter-xxx来加快我们的开发流程,创建项目时就可以看到可供选择的各种spring-boot-starter-xxx,那么这么多的spring-boot-starter-xxx,我们是否都需要了解呢,如果项目中需要用到某一个功能,是否就应该加入这个spring-boot-starter-xxx呢?

笔者人为,spring-boot-starter-xxx提供的完整jar包依赖和自动配置固然很好,但是当我们要在项目中加入某一个功能时,作为开发人员,是应该清楚的知道该功能的依赖关系和配置逻辑的,所以并不一定需要引入SpringBoot的spring-boot-starter-xxx,而且SpringBoot对这些spring-boot-starter-xxx做的自动配置,如果我们并不熟悉和十分清楚,往往会给我们开发人员造成不明所以的困扰,所以,笔者建议,在对SpringBoot提供的某一个spring-boot-starter-xxx所提供的功能并不十分清楚时,还是使用配置类的方式吧。

还有,由于某些自动配置类的激活是根据项目中是否包含某个class或容器中是否注册了某个bean,所以笔者建议,如果项目中引入了新的jar包,或者手工注册了某个bean,都要通过debug的方式查看是否开启了某个自动配置。

另外,本文代码只是为了辅助说明,比如DriverManagerDataSource正式环境不建议使用,请更换为其它数据源,比如BasicDataSource

本文示例代码下载地址:https://github.com/hanqunfeng/SpringBootStudy