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
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


Spring Boot学习笔记04--数据访问_第1张图片

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


    org.springframework.boot
    spring-boot-starter-jdbc

配置项目

在pom中增加MySQL依赖


    mysql
    mysql-connector-java
    5.1.37

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

#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

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

@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 getAllPersonList(){
        String sql = "select * from person s";
        List list = jdbcTemplate.query(sql,new PersonMapper());
        return list;
    }

    class PersonMapper implements RowMapper{
        @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

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;

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

    }
    public List getAllPersonList(){
        return personDao.getAllPersonList();
    }
}

单元测试:SpringbootjdbcdemoApplicationTests

@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 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的自动配置功能了。

    

    
        org.springframework
        spring-jdbc
        4.3.4.RELEASE
    

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

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class SpringbootjdbcdemoApplication {

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

创建DataSourceConfig配置类

@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中添加数据源配置信息

#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配置类中增加如下内容:

@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框架提供的,配置方法参考下面的代码

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

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

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

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class SpringbootjdbcdemoApplicationTests {…………}

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

@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进行了演示,接下来我们添加多个事务管理器。

@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注解中指定要使用哪一个事务管理器

@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 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 getAllPersonList2(){
        return personDao.getAllPersonList2();
    }
}


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

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


多数据源事务管理

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

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


    com.atomikos
    transactions-jdbc
    4.0.4


    com.atomikos
    transactions-jta
    4.0.4


    com.atomikos
    transactions
    4.0.4


    com.atomikos
    atomikos-util
    4.0.4

 
    javax.transaction
    jta
    1.1

DataSourceConfig进行改造:

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的相关属性进行配置,不过也可以不加这个文件,因为所有的属性都有默认值。

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


    org.hibernate
    hibernate-core
    4.3.5.Final

DataSourceConfig

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;
    }
}
@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代码如下:

package com.example.hibernate;

import java.util.List;

public interface CP_HibernateDAO {

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

    public void save(Object entity);

}
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中添加依赖


    org.mybatis.spring.boot
    mybatis-spring-boot-starter
    1.1.1

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

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

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

@Mapper
public interface PersonMapper {
    //………………
}

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


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


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

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

        

        
        
            org.mybatis
            mybatis
            3.4.0
        
        
            org.mybatis
            mybatis-spring
            1.3.0
        
        
            org.springframework
            spring-jdbc
            4.3.4.RELEASE
        

创建MyBatisConfig

@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注解

@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

        
            org.springframework
            spring-jdbc
            4.3.4.RELEASE
        

        
        
            org.mybatis
            mybatis
            3.4.0
        
        
            org.mybatis
            mybatis-spring
            1.3.0
        

        
            com.atomikos
            transactions-jdbc
            4.0.4
        
        
            com.atomikos
            transactions-jta
            4.0.4
        
        
            com.atomikos
            transactions
            4.0.4
        
        
            com.atomikos
            atomikos-util
            4.0.4
        

        
            javax.transaction
            jta
            1.1
        

        
            mysql
            mysql-connector-java
            5.1.37
        

MyBatisConfig

@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

@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

你可能感兴趣的:(Spring Boot学习笔记04--数据访问)