前言
SpringBoot 整合多数据源的 Mybatis
。
实现
创建一个新的 SpringBoot 项目。
本项目以连接两个数据源为例(数据源为本地 MySQL 中两个不同的数据库),大家按照实际需求进行连接。
pom.xml
在 pom.xml
文件引入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-android</version>
<scope>compile</scope>
</dependency>
注:
- 由于本项目连接的是两个 MySQL 数据源,如果要连接其它类型的数据库需要导入对应的依赖。
application.properties
在 application.properties
中添加如下配置项:
# 数据源1
spring.datasource.datasource1.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.datasource1.username=root
spring.datasource.datasource1.password=
spring.datasource.datasource1.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源2
spring.datasource.datasource2.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.datasource2.username=root
spring.datasource.datasource2.password=
spring.datasource.datasoutce2.driver-class-name=com.mysql.cj.jdbc.Driver
# 开启MyBatis的SQL语句输出
logging.level.com.langjialing.mybatismultisourcedemo.mapper.*=debug
Config配置类
我们把数据源的连接配置均写在 Config 配置类中,包括 Mapper 接口类和 Mapper 文件。
Datasource1Config.java
第一个数据源的配置类:
package com.langjialing.mybatismultisourcedemo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @author 郎家岭伯爵
* @time 2023/7/20 13:52
*/
@Configuration
@MapperScan(basePackages = {Datasource1Config.PACKAGE},
sqlSessionFactoryRef = "datasource1SessionFactory")
public class Datasource1Config {
static final String PACKAGE = "com.langjialing.mybatismultisourcedemo.mapper.datasource1";
static final String MAPPER_LOCATION = "classpath:mapper/datasource1/*.xml";
static final String CONFIG_LOCATION = "classpath:mybatis-config.xml";
@Bean(name = "datasource1DruidDatasource")
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DruidDataSource datasource1DruidDatasource() {
return new DruidDataSource();
}
@Bean(name = "datasource1SessionFactory")
@Primary
public SqlSessionFactory datasource1SessionFactory(@Qualifier("datasource1DruidDatasource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(MAPPER_LOCATION));
sessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(CONFIG_LOCATION));
sessionFactory.setObjectWrapperFactory(new MapWrapperFactory());
return sessionFactory.getObject();
}
@Bean(name = "datasource1TransactionManager")
public DataSourceTransactionManager datasource1TransactionManager() {
return new DataSourceTransactionManager(datasource1DruidDatasource());
}
@Bean(name = "datasource1JdbcTemplate")
public JdbcTemplate datasource1JdbcTemplate(@Qualifier("datasource1DruidDatasource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
@Configuration
:这个注解标识这是一个配置类,告诉 SpringBoot 在启动时要读取该类中的配置。@MapperScan
:这个注解用于扫描指定包下的 MyBatis Mapper 接口,并注册这些 Mapper 接口的实现到 Spring 容器中。在这里,@MapperScan
扫描了包路径为com.langjialing.mybatismultisourcedemo.mapper.datasource1
的Mapper
接口,将其注册到 Spring 容器中,并指定使用名为datasource1SessionFactory 的 SqlSessionFactory
。@Bean
:这个注解用于标识一个方法产生一个Spring Bean
对象,并将该对象注册到 Spring 容器中。在这里,@Bean
注解用于标识四个方法分别创建了不同的 Bean 实例。这些 Bean 包括数据源(DataSource)
、SqlSessionFactory
、事务管理器(DataSourceTransactionManager)
和JdbcTemplate
。其中,SqlSessionFactory
对象通过SqlSessionFactoryBean
配置了 Mapper 文件的路径、MyBatis 配置文件的路径以及自定义的对象包装工厂。@ConfigurationProperties(prefix = "spring.datasource.datasource1")
:这个注解用于读取以spring.datasource.datasource1
为前缀的配置属性,并将它们绑定到datasource1DruidDatasource()
方法创建的DruidDataSource
对象中。@Qualifier("datasource1DruidDatasource")
:这个注解用于指定依赖注入时所使用的 bean 名称。在这里,datasource1SessionFactory()
和datasource1JdbcTemplate()
方法中,通过@Qualifier
注解指定使用名为datasource1DruidDatasource
的DataSource Bean
。@Primary
:这个注解用于解决多个相同类型的 Bean 注入时的歧义性。在这里,datasource1SessionFactory()
方法上使用了@Primary
注解,表示当存在多个SqlSessionFactory Bean
时,优先使用该方法所返回的SqlSessionFactory Bean
。
Datasource2Config.java
第二个数据源的配置:
package com.langjialing.mybatismultisourcedemo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @author 郎家岭伯爵
* @time 2023/7/20 13:52
*/
@Configuration
@MapperScan(basePackages = {Datasource2Config.PACKAGE},
sqlSessionFactoryRef = "datasource2SessionFactory")
public class Datasource2Config {
static final String PACKAGE = "com.langjialing.mybatismultisourcedemo.mapper.datasource2";
static final String MAPPER_LOCATION = "classpath:mapper/datasource2/*.xml";
static final String CONFIG_LOCATION = "classpath:mybatis-config.xml";
@Bean(name = "datasource2DruidDatasource")
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DruidDataSource dataSource2DruidDatasource() {
return new DruidDataSource();
}
@Bean(name = "datasource2SessionFactory")
@Primary
public SqlSessionFactory datasource2SessionFactory(@Qualifier("datasource2DruidDatasource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(MAPPER_LOCATION));
sessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(CONFIG_LOCATION));
return sessionFactory.getObject();
}
@Bean(name = "datasource2TransactionManager")
public DataSourceTransactionManager datasource2TransactionManager() {
return new DataSourceTransactionManager(dataSource2DruidDatasource());
}
@Bean(name = "datasource2JdbcTemplate")
public JdbcTemplate datasource2JdbcTemplate(@Qualifier("datasource2DruidDatasource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
其它配置类
在第一个数据源的配置类中有一行代码 sessionFactory.setObjectWrapperFactory(new MapWrapperFactory());
,通过设置这个属性,可以来处理特定类型的查询结果对象,或者实现特定的属性访问规则。但不设置也是可以正常使用的。
MapWrapperFactory.java
:
package com.langjialing.mybatismultisourcedemo.config;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import java.util.Map;
/**
* @author 郎家岭伯爵
* @time 2023/7/21 9:56
*/
public class MapWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object object) {
return object != null && object instanceof Map;
}
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return new CameCaseMapWrapper(metaObject, (Map) object);
}
}
-
hasWrapperFor(Object object)
方法用于判断给定对象是否需要用该ObjectWrapperFactory
进行处理。在这个实现中,它判断给定的对象是否为非空,并且是Map
类型的对象。如果是,返回true
,表示该ObjectWrapperFactory
可以处理这个对象。 -
getWrapperFor(MetaObject metaObject, Object object)
方法在需要处理该对象时被调用,用于创建相应的ObjectWrapper
。在这个实现中,它返回了一个自定义的CameCaseMapWrapper
对象,它继承自MapWrapper
。
CameCaseMapWrapper.java
:
package com.langjialing.mybatismultisourcedemo.config;
import com.google.common.base.CaseFormat;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.MapWrapper;
import java.util.Map;
/**
* @author 郎家岭伯爵
* @time 2023/7/21 9:57
*/
public class CameCaseMapWrapper extends MapWrapper {
public CameCaseMapWrapper(MetaObject metaObject, Map<String, Object> map) {
super(metaObject, map);
}
@Override
public String findProperty(String name, boolean useCamelCaseMapping) {
if (useCamelCaseMapping) {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name);
}
return name;
}
}
-
CameCaseMapWrapper
继承自MapWrapper
,它通过调用父类的构造方法,传入metaObject
和map
来初始化。 -
findProperty(String name, boolean useCamelCaseMapping)
是MapWrapper
中的一个方法,用于查找属性名。在这个实现中,useCamelCaseMapping
参数表示是否启用驼峰命名映射。如果启用了驼峰命名映射(useCamelCaseMapping
为true
),则将会对属性名进行转换。 -
在
findProperty
方法中,如果启用了驼峰命名映射,它使用Guava
的CaseFormat
类将数据库下划线命名风格转换为 Java 驼峰命名风格。具体来说,它将字符串 name 从数据库下划线命名风格转换为 Java 驼峰命名风格。例如,对于数据库列名first_name
,在启用了驼峰命名映射后,findProperty
方法会将它转换为firstName
,以符合 Java 的命名约定。
Mapper接口类
Datasource1Mapper.java
第一个数据源的 Mapper 接口类:
package com.langjialing.mybatismultisourcedemo.mapper.datasource1;
import com.langjialing.mybatismultisourcedemo.entity.User;
import java.util.List;
/**
* @author 郎家岭伯爵
* @time 2023/7/20 13:56
*/
public interface Datasource1Mapper {
/**
* 获取所有用户。
* @return 所有用户。
*/
List<User> getAll();
/**
* 根据用户ID获取用户。
* @param id 用户ID。
* @return 用户。
*/
User getOne(Long id);
/**
* 新增用户。
* @param user 用户。
*/
void insert(User user);
/**
* 更新用户。
* @param user 用户。
*/
void update(User user);
/**
* 根据用户ID删除用户。
* @param id 用户ID。
*/
void delete(Long id);
}
Datasource2Mapper.java
第二个数据源的 Mapper 接口类 :
package com.langjialing.mybatismultisourcedemo.mapper.datasource2;
import com.langjialing.mybatismultisourcedemo.entity.User;
import java.util.List;
/**
* @author 郎家岭伯爵
* @time 2023/7/20 13:57
*/
public interface Datasource2Mapper {
/**
* 获取所有用户。
* @return 所有用户。
*/
List<User> getAll();
/**
* 根据用户ID获取用户。
* @param id 用户ID。
* @return 用户。
*/
User getOne(Long id);
/**
* 新增用户。
* @param user 用户。
*/
void insert(User user);
/**
* 更新用户。
* @param user 用户。
*/
void update(User user);
/**
* 根据用户ID删除用户。
* @param id 用户ID。
*/
void delete(Long id);
}
Mapper配置文件
Datasource1Mapper.xml
第一个数据源的 Mapper 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.langjialing.mybatismultisourcedemo.mapper.datasource1.Datasource1Mapper">
<resultMap id="BaseResultMap" type="com.langjialing.mybatismultisourcedemo.entity.User">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="age" property="age"/>
<result column="password" property="password"/>
</resultMap>
<sql id="Base_Column_List">
user_id, user_name, age, password
</sql>
<select id="getAll" resultType="com.langjialing.mybatismultisourcedemo.entity.User">
select
<include refid="Base_Column_List" />
from `user`
</select>
<select id="getOne" parameterType="java.lang.Long" resultType="com.langjialing.mybatismultisourcedemo.entity.User">
SELECT
<include refid="Base_Column_List" />
FROM `user`
WHERE user_id = #{id}
</select>
<insert id="insert" parameterType="com.langjialing.mybatismultisourcedemo.entity.User">
insert into
`user`
(user_name,age,password)
values
(#{userName},#{age},#{password});
</insert>
<update id="update" parameterType="com.langjialing.mybatismultisourcedemo.entity.User">
update
`user`
set
<if test="userName != null">user_name=#{userName},</if>
<if test="password != null">password=#{password},</if>
age=#{age}
where user_id=#{userId}
</update>
<delete id="delete">
delete from
`user`
where
user_id=#{id}
</delete>
</mapper>
Datasource2Mapper.xml
第二个数据源的 Mapper 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.langjialing.mybatismultisourcedemo.mapper.datasource2.Datasource2Mapper">
<resultMap id="BaseResultMap" type="com.langjialing.mybatismultisourcedemo.entity.User">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="age" property="age"/>
<result column="password" property="password"/>
</resultMap>
<sql id="Base_Column_List">
user_id, user_name, age, password
</sql>
<select id="getAll" resultType="com.langjialing.mybatismultisourcedemo.entity.User">
select
<include refid="Base_Column_List" />
from `user`
</select>
<select id="getOne" parameterType="java.lang.Long" resultType="com.langjialing.mybatismultisourcedemo.entity.User">
SELECT
<include refid="Base_Column_List" />
FROM `user`
WHERE user_id = #{id}
</select>
<insert id="insert" parameterType="com.langjialing.mybatismultisourcedemo.entity.User">
insert into
`user`
(user_name,age,password)
values
(#{userName},#{age},#{password});
</insert>
<update id="update" parameterType="com.langjialing.mybatismultisourcedemo.entity.User">
update
`user`
set
<if test="userName != null">user_name=#{userName},</if>
<if test="password != null">password=#{password},</if>
age=#{age}
where user_id=#{userId}
</update>
<delete id="delete">
delete from
`user`
where
user_id=#{id}
</delete>
</mapper>
Mybatis-config.xml
Mybatis-config 配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 设置全局配置项 -->
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启自动映射的时候将未知的列映射到属性 -->
<setting name="callSettersOnNulls" value="true"/>
</settings>
<!-- 设置类型别名 -->
<typeAliases>
<!-- <typeAlias type="com.example.model.User" alias="User"/>-->
<!-- 可以添加其他类型别名 -->
</typeAliases>
<!-- 设置插件(可选) -->
<!--
<plugins>
<plugin interceptor="com.example.mybatis.MyPlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
-->
<!-- 设置映射器(Mapper)的位置 -->
<mappers>
<!-- <mapper resource="com/example/mapper/UserMapper.xml"/>-->
<!-- 可以添加其他Mapper文件的位置 -->
</mappers>
</configuration>
注:
- 这个文件不加也是可以的,这样会加载默认配置。但不加这个文件的话要注意把
Datasource1Config.java
和Datasource2Config.java
中的相关配置也去掉。
User实体类
package com.langjialing.mybatismultisourcedemo.entity;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
/**
* @author 郎家岭伯爵
* @time 2023/7/20 14:33
*/
@Data
@Builder
public class User {
private Integer userId;
private Integer age;
private String userName;
private String password;
@Tolerate
User() {}
}
Controller层
在 Controller 层添加接口验证多数据源:
package com.langjialing.mybatismultisourcedemo.controller;
import com.langjialing.mybatismultisourcedemo.entity.User;
import com.langjialing.mybatismultisourcedemo.mapper.datasource1.Datasource1Mapper;
import com.langjialing.mybatismultisourcedemo.mapper.datasource2.Datasource2Mapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author 郎家岭伯爵
* @time 2023/7/20 14:37
*/
@RestController
public class DatasourceController {
@Resource
private Datasource1Mapper datasource1Mapper;
@Resource
private Datasource2Mapper datasource2Mapper;
@GetMapping("/datasource1/getAll")
public List<User> getAllFromDatasource1() {
return datasource1Mapper.getAll();
}
@GetMapping("/datasource2/getAll")
public List<User> getAllFromDatasource2() {
return datasource2Mapper.getAll();
}
}
注:
- 这里我们只写了一个
getAll()
方法,其它方法的使用与上一篇文章中的 Controller 是一样的,不再赘述。
测试多数据源
启动项目后,调用 Controller 层提供的接口。
总结
SpringBoot 整合多数据源的 Mybatis
。