在实际的Java开发中,不可避免的要对数据持久化,常用的持久化技术有MyBatis、Spring自带的JdbcTemplate和Spring Boot提供的Jpa规范。在这篇文章中,我们会逐个讲解者三种方式的使用及多数据源的配置。
目录
大家一定都有这样的经历,刚开始学Java操作数据库时,当时没有什么框架可以使用,就使用原生的JDBC来操作数据,导致代码繁琐且冗余度高。为了解决这样的问题,Spring对JDBC做了封装形成了JdbcTemplate,它可以使用Spring的注入功能将数据源(DataSource)注入到JdbcTemplate中,Spring中的IOC容器可以将数据源当做Java Bean一样管理,依次简化了对数据库的操作。
JdbcTemplate使用很简单,只需要我们在Spring Boot项目中添加数据库驱动和Druid数据源依赖即可
<!--配置数据源-->
<!--druid和druid-spring-boot-starter的区别是,后者能够配置多个数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
NOTE**:选取数据源依赖时,有两种选择,分别是druid和druid-spring-boot-starter,它们的区别是druid-spring-boot-starter可以配置多数据源
application.properties主要是链接数据库的一些基本信息,如数据库的用户名、密码、数据源类型、驱动等等。具体的配置如下:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
在使用之前,我们首先创建一个实体类与指定数据库中的表对应起来,我们这里创建一个User Bean
public class User {
private Long id;
private String username;
private String address;
//省略getter/setter
}
接下来就是使用JdbcTemplate进行增删改查,除了几个查询的API,增删改都是采用update操作,只需要在update方法中传入SQL语句即可。
/**
* 添加操作
*/
public Integer addUser(User user){
return jdbcTemplate.update("insert into user(username,address) values (?,?)",user.getUsername(),user.getAddress());
}
/**
* 修改操作
*/
public Integer updateUser(User user){
return jdbcTemplate.update("update user set username=?,address=? where id=?",user.getUsername(),user.getAddress(),user.getId());
}
/**
* 删除操作
*/
public Integer deleteUserById(Integer id) {
return jdbcTemplate.update("delete from user where id=?", id);
}
/**
* 查询操作
* getAllUsers1适用于数据库中表的字段名和实体中字段名不一致的情况
*/
public List<User> getAllUsers1(){
return jdbcTemplate.query("select * from user", new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user=new User();
int id=resultSet.getInt("id");
String username=resultSet.getString("username");
String address=resultSet.getString("address");
user.setUsername(username);
user.setId(id);
user.setAddress(address);
return user;
}
});
}
/**
* 当实体类的属性和数据库表中的字段一致时,可以采用此方法
*/
public List<User> getAllUsers2(){
return jdbcTemplate.query("select * from user",new BeanPropertyRowMapper<>(User.class));
}
我们在开发中不可避免的要配置多数据源,JdbcTemplate配置多数据源相对MyBatis和Jpa相对简单些,只需要在修改数据源依赖、添加多数据源信息和增加相应的配置文件即可,下面我们逐个详细介绍
将依赖由druid改为druid-spring-boot-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
在application.properties定义两个数据库信息spring-boot-study和spring-boot-study2
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.one.jdbc-url=jdbc:mysql://127.0.0.1:3306/spring-boot-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.two.jdbc-url=jdbc:mysql://127.0.0.1:3306/spring-boot-study2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.two.username=root
spring.datasource.two.password=root
因为我们在application.properties文件中修改了配置信息,所以我么需要重新定义数据源配置文件(DataSourceConfig)和JdbcTemplateConfig
@Configuration
public class DataSourceConfig {
@Bean
//因为我们在配置文件中修改了名称,为了使得配置类能够正确找到数据源,需要配置前缀
@ConfigurationProperties(prefix = "spring.datasource.one")
DataSource sourceOne(){
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.two")
DataSource sourceTwo(){
return DataSourceBuilder.create().build();
}
}
JdbcTemplateConfig配置
@Configuration
public class JdbcTemplateConfig {
//以为有两个数据源,所以我们要使用@Qualifier确定那个是我们想要的
@Bean
JdbcTemplate jdbcTemplateOne(@Qualifier("sourceOne") DataSource sourceOne) {
return new JdbcTemplate(sourceOne);
}
@Bean
JdbcTemplate jdbcTemplateTwo(@Qualifier("sourceTwo") DataSource sourceTwo) {
return new JdbcTemplate(sourceTwo);
}
}
MyBatis是采用java编写的一个持久层框架,它同样封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动、创建链接等繁杂过程,使用ORM思想实现了对结果集的封装。
和JdbcTemplate不同的是,MyBatis是第三方框架,因此在添加数据库驱动的基础上,需要额外的引入依赖,
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
和JdbcTemplate相同,都需要在application.properties文件上配置连接数据库的信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
在使用之前,需要为创建实体类与数据库中的表对应,并创建Mapper.xml用于操作SQL语句
public class User {
private Integer id;
private String username;
private String address;
......
}
mapper.xml文件是建立起数据库表与实体类的映射,使用SQL语句将结果返回给具体的方法。mapper.xml是默认放在resources目录下,其目录设置要同mapper方法接口的层级结构相同。如果想将mapper.xml放在其他位置,需要重新配置文件的扫描路径,假如mapper.xml同mapper接口方法接口放在同一包下,则需要对pom修改为
<!--配置扫描mapper.xml文件的位置-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml
src/main/resources
上文中的2.1和2.2是对单个数据库表进行的配置操作,在实际的应用场景中,需要对多个不同的数据库表进行操作,所以非常有必要学习多数据源的配置。
同JdbcTemplate一样,当使用多个数据源时,需要将druid依赖由druid改为druid-spring-boot-starter。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
同JdbcTemplate一样,需要在application.properties定义两个数据库信息spring-boot-study和spring-boot-study2作为测试
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.one.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.two.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.two.username=root
spring.datasource.two.password=root
因为我们配置两个数据库表,所以除了我们需要分别编写配置文件来各自处理不同的mapper。
同JdbcTemplate相同,我们需要编写配置文件(DataSourceConfig)来分别处理
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.one")
DataSource sourceOne() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.two")
DataSource sourceTwo() {
return DruidDataSourceBuilder.create().build();
}
}
@ConfigurationProperties(prefix = “spring.datasource.one”)是为了定义不同的数据库表。
配置MyBatisConfig1是为了对应mapper1
@Configuration
@MapperScan(basePackages = "com.simon.mybatis2.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
@Resource(name = "sourceOne")
DataSource sourceOne;
@Bean
SqlSessionFactory sqlSessionFactory1() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
try {
bean.setDataSource(sourceOne);
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Bean
SqlSessionTemplate sqlSessionTemplate1() {
return new SqlSessionTemplate(sqlSessionFactory1());
}
}
在单数据库表下,使用MyBatis框架持久化数据是按照以下流程
对应的代码为
//导入配置文件,SqlMapConfig.xml配置的是数据源即数据库信息
InputStream in= Resources.getResourceAsStream("SqlMapConfig.xml");
//创建sqlSessionFactory的构建者对象
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
//使用构建者创建工厂对象sqlSessionFactory
SqlSessionFactory factory=builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
而在我们配置文件MyBatisConfig1用到了SqlSessionFactory、SqlSessionTemplate,SqlSessionFactoryBean,因此会有同学有疑问,SqlSessionFactory、SqlSessionTemplate,SqlSessionFactoryBean和SqlSession之间的关系是什么
SqlSession实现了Closeable接口,是一种可关闭的连接,可以表示数据库客户端和数据库服务端之间的一种会话,并维护了两者之间的状态信息。SqlSession接口内有用于操作数据库执行sql语句的select、insert、update等方法。
SqlSessionTemplate是SqlSession的具体实现类,除了实现SqlSession,它还实现了DisposableBean接口,这表明,SqlSessionTemplate的实例被Bean工厂发现后,会把他们纳入整个spring bean生命周期的管理过程之中,当BeanFactory尝试销毁时,Beans的管理者会以回调的方式调用SqlSessionTemplate的destroy()方法。因此,我们就可以执行Dao层的sql语句
SqlSessionFactory也是一个接口,是生产SqlSession工厂(采用动态代理的方式),它可以打开一个SqlSession会话,而且重载了许多不同的参数,你可以改变这些参数自定义会话过程中的一些默认行为。例如:可以设置自动提交事务或是关闭自动提交;可以设置获取数据库连接的线程的类型(重用,每次新产生等等);也可以获取整个MyBatis的配置信息的Configuration对象实例等等。
SqlSessionFactoryBean实现了FactorBean接口,表示SqlSessionFactoryBean的实例不再是一个普通的bean对象,而是可以产生自己Bean(SqlSessionFactory)的一个工厂,并且产生的Bean会被纳入spring的生命周期。
总结
SqlSessionFactoryBean是生产SqlSessionFactory的一种工厂bean。SqlSessionFactory是打开SqlSession会话的工厂,是一个接口,可以根据需求自己实现,它的默认实现类DefaultSqlSessionFactory使用了数据库连接池技术。SqlSession是客户端和数据库服务端之间的会话信息,里面有许多操作数据库的方法。SqlSessionTemplate是SqlSession的一个具体实现。
MyBatisConfig2同MyBatisConfig1一样,只不过它对应的是mapper2
@Configuration
@MapperScan(basePackages = "com.simon.mybatis2.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
@Resource(name = "sourceTwo")
DataSource sourceTwo;
@Bean
SqlSessionFactory sqlSessionFactory2() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
try {
bean.setDataSource(sourceTwo);
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Bean
SqlSessionTemplate sqlSessionTemplate2() {
return new SqlSessionTemplate(sqlSessionFactory2());
}
}
Jpa(Java Persistence API)是SUN公司提出的ORM规范,提供了一种对象/映射工具来管理Java应用中的关系数据,使用XML或者注解的方式简化对数据库的操作。
它的出现是为了规范现有的ORM框架,采用Jpa开发时,我们仍将使用这些ORM框架,只是此时开发出来的应用不在依赖于某个持久化提供商。应用可以在不修改代码的情况下载任何JPA环境下运行,真正做到低耦合,可扩展的程序设计。类似于JDBC,在JDBC出现以前,我们的程序针对特性的数据库API进行编程,但是现在我们只需要针对JDBC API编程,这样能够在不改变代码的情况下就能换成其他的数据库。
而Spring-Data-jpa是在Jpa规范下提供的Resporsity实现,JpaResporsity拥有常用的CURD方法及分页、字段排序等,可以统一不同ORM框架对数据库操作的代码。
在Pom文件中添加MySQL驱动和spring-boot-starter-data-jpa依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
在application.properties配置文件中添加数据库表的信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
#jpa是自动生成Sql语句的,是否在命令行展示SQL语句
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.database-platform=mysql
#jpa可以根据实体类自动的数据库创建表,update表示,表存在时更新,不存在时创建
spring.jpa.hibernate.ddl-auto=update
#根据不同的数据版本确定,用于确定引擎,InnoDB
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
在使用之前,需要编写一个实体类与数据库中的表对应
@Entity(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String address;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
注解@Entity表明这是个实体类,并且可以根据实体类中的信息创建相应的表放在数据库中。@Id表明对用数据库表中的主键Id(必须有), @GeneratedValue(strategy = GenerationType.IDENTITY)表是Id自增长。
public interface UserDao extends JpaRepository<User,Integer> {
}
如果仅仅使用常见的CURD操作,接口中不需要写自定义的方法,JpaRepository已经实现常用的CURD操作,可以满足我们日常的操作。
同JdbcTemplate、MyBatis一样,当使用多个数据源时,需要将druid依赖由druid改为druid-spring-boot-starter。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
3.3.2添加数据库信息
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.one.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.two.url=jdbc:mysql://127.0.0.1:3306/spring-boot-study2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
spring.datasource.two.username=root
spring.datasource.two.password=root
spring.jpa.properties.show-sql=true
spring.jpa.properties.database=mysql
spring.jpa.properties.database-platform=mysql
spring.jpa.properties.hibernate.ddl-auto=update
spring.jpa.properties.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.one")
DataSource sourceOne(){
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.two")
DataSource sourceTwo() {
return DruidDataSourceBuilder.create().build();
}
}
配置JpaConfig1是为了对应Dao1
@Configuration
@EnableJpaRepositories(basePackages = "com.simon.jpa2.dao1",entityManagerFactoryRef = "localContainerEntityManagerFactoryBean1",transactionManagerRef = "platformTransactionManager1")
public class JpaConfig1 {
@Autowired
@Qualifier("sourceOne")
DataSource sourceOne;
@Autowired
JpaProperties jpaProperties;
@Bean
@Primary
/**
* JPA EntityManagerFactory实例的构建器,允许你通过一个构建器模式创建一个或多个LocalContainerEntityManagerFactoryBean,并做一些常见配置。
*/
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean1(EntityManagerFactoryBuilder builder) {
return builder.dataSource(sourceOne)
.properties(jpaProperties.getProperties())
.persistenceUnit("unit1")
.packages("com.simon.jpa2.bean")
.build();
}
@Bean
PlatformTransactionManager platformTransactionManager1(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(localContainerEntityManagerFactoryBean1(builder).getObject());
}
}
配置JpaConfig1是为了对应Dao2
@Configuration
@EnableJpaRepositories(basePackages = "com.simon.jpa2.dao2",entityManagerFactoryRef = "localContainerEntityManagerFactoryBean2",transactionManagerRef = "platformTransactionManager2")
public class JpaConfig2 {
@Autowired
@Qualifier("sourceTwo")
DataSource sourceTwo;
@Autowired
JpaProperties jpaProperties;
@Bean
@Primary
/**
* JPA EntityManagerFactory实例的构建器,允许你通过一个构建器模式创建一个或多个LocalContainerEntityManagerFactoryBean,并做一些常见配置。
*/
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean2(EntityManagerFactoryBuilder builder) {
return builder.dataSource(sourceTwo)
.properties(jpaProperties.getProperties())
.persistenceUnit("unit2")
.packages("com.simon.jpa2.bean")
.build();
}
@Bean
PlatformTransactionManager platformTransactionManager2(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(localContainerEntityManagerFactoryBean2(builder).getObject());
}
}
参考:
[1]https://www.cnblogs.com/xichji/p/12342569.html
[2]https://blog.csdn.net/android_bar/article/details/81040580
[3]https://gitee.com/lenve/javaboy-video-samples/tree/master/%E7%AC%AC%2005%20%E7%AB%A0%20Spring%20Boot%20%E6%95%B4%E5%90%88%E6%8C%81%E4%B9%85%E5%B1%82%E6%8A%80%E6%9C%AF