目录
1、这里我只使用两个数据源进行配置操作说明。导入依赖就不用说了吧
2.1、数据库连接池--druid的配置 (单个的druid监控,看新的配置就可以了)
一、介绍
平时我们使用的springboot配置单个数据源实现数据的增删改查操作,同时配备事务规则;但实际开发中,并不会只对一个数据库实现操作,如何实现多个数据源的操作的同时,也能都满足事务(同时添加数据到多个数据库中,一条失败,其他均需要失败)呢?这时候我们就需要进行多数据源事务的配置了。
二、单个数据源以及事务的配置(springboot中)
server:
port: 80
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: cn.linkpower.vo # 定义所有操作类的别名所在包
mapper-locations: # 所有的mapper映射文件
- classpath:mybatis/mapper/**/*.xml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置当前要使用的数据源的操作类型
driver-class-name: org.gjt.mm.mysql.Driver # 配置MySQL的驱动程序类
url: jdbc:mysql://127.0.0.1:3306/mldn # 数据库连接地址
username: root # 数据库用户名
password: root # 数据库连接密码
filters: stat,wall,log4j # 开启druid 监控 还需要进行一项过滤配置
dbcp2: # 进行数据库连接池的配置
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化提供的连接数
max-total: 10 # 最大的连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
只需要在service的实现类上注解@Transactional即可进行事务的处理操作
三、单个数据源的事务如此简单,那多个呢?
1、这里我只使用两个数据源进行配置操作说明。导入依赖就不用说了吧
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboottestdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<!-- 设置相关导入包的编码属性 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web开发 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 测试开发 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- springboot整合MyBatis操作 -->
<!-- 1.配置druid数据库连接池 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.0</version>
</dependency>
<!-- 2.导入mybatis库文件 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- 使用aop拦截器 需要的aop库文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 使用thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 阿里巴巴json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<!-- 省略get/set(开发工具需要安装lomback的插件 https://blog.csdn.net/qq_25646191/article/details/79639633) -->
<!-- <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency> -->
<!-- 热部署 start -->
<!-- 热部署方法1spring boot devtools 依赖包 -->
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency> -->
<!-- 热部署方式2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
<!-- 热部署 end -->
</dependencies>
<build>
<plugins>
<!-- 打包成jar 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、相关配置
依赖导入成功后,我们需要配置数据源和全局拦截异常等操作(一般的项目都会有像token过滤的处理),此处使用的是springboot2.0及以上的配置方式实现除了数据源和事务的配置之外的配置!
2.1、数据库连接池--druid的配置 (单个的druid监控,看新的配置就可以了)
旧的配置:配置方面在spring 2.X实际使用时,出现了一些莫名奇妙的问题
package com.example.config;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
/**
* 拓展技能:进行druid数据库连接池的性能的监控操作 需要做一些配置(例如:你访问的ip地址是否是白名单、数据库sql监控等)<br>
* 访问方式: http://localhost/druid/
* @author 76519
*
*/
//@Configuration
public class DruidConfig {
private Logger log = LoggerFactory.getLogger(DruidConfig.class);
//@Bean
public ServletRegistrationBean druidServlet() { // 主要实现WEB监控的配置处理
//System.out.println("WEB监控的配置处理");
this.log.info("WEB监控的配置处理");
//想用使用 Servlet 功能,就必须要借用 Spring Boot 提供的 ServletRegistrationBean 接口
//访问方式http://localhost/druid/getAll 启动alibaba的druid数据库连接池自带的servlet
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
new StatViewServlet(), "/druid/*"); // 现在要进行druid监控的配置处理操作
servletRegistrationBean.addInitParameter("allow",
"127.0.0.1,192.168.28.159"); // 白名单
servletRegistrationBean.addInitParameter("deny", "192.168.28.200"); // 黑名单
servletRegistrationBean.addInitParameter("loginUsername", "admin"); // 用户名
servletRegistrationBean.addInitParameter("loginPassword", "hello"); // 密码
servletRegistrationBean.addInitParameter("resetEnable", "false"); // 是否可以重置数据源
return servletRegistrationBean ;
}
//@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean() ;
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*"); // 所有请求进行监控处理
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.css,/druid/*");
return filterRegistrationBean ;
}
//@Bean
//@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
新的配置:
- pom文件中新增依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- application.yml修改配置项
server:
port: 80 #设置端口号信息
mybatis:
config-location: classpath:mybatis/mybatis-config.xml # mybatis配置文件所在路径
type-aliases-package: cn.linkpower.vo # 定义所有操作类的别名所在包
mapper-locations: # 所有的mapper映射文件
- classpath:mybatis/mapper/**/*.xml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/fhadmin?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 配置Druid的其他参数,以下配置必须增加一个配置文件才能有效
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat, wall
# 打开PSCache,并且指定每个连接上PSCache的大小
maxPoolPreparedStatementPerConnectionSize: 20
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
- DruidConfig.java配置类重新定义
@Configuration
public class DruidConfig {
/**
* @function 添加
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druid(){
return new DruidDataSource();
}
//配置Druid的监控
//1、配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","");//默认就是允许所有访问
initParams.put("deny","192.168.15.21");
bean.setInitParameters(initParams);
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
新的druid配置,针对2.X,参考博客
这个配置类我之前使用过,是可以进行sql监控的,只是为了进行多数据源事务的测试将其注释了!
2.2、全局异常的拦截配置
package com.example.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* 异常拦截页面
* @author 76519
*
*/
@Configuration
public class ExceptionConfiguration implements HandlerExceptionResolver{
private Logger log = LoggerFactory.getLogger(ExceptionConfiguration.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
log.info("监听异常");
//获取异常信息 跳转异常界面
String exStr = ex.toString().replaceAll("\n", "<br/>");
ModelAndView mv = new ModelAndView();
mv.addObject("exception", exStr);
mv.setViewName("error");
return mv;
}
}
2.3、过滤拦截的配置
有两部分构成,第一部分需要注册需要拦截的请求是什么;第二部分则对需要过滤的请求实现拦截操作
注册配置
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.example.config.util.BaseInterceptor;
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("****InterceptorConfiguration****");
registry.addInterceptor(new BaseInterceptor()).addPathPatterns("/**/demo");//拦截处理操作的匹配路径
//2.0以前的配置需要 继承 WebMvcConfigurerAdapter 抽象类 重写此方法后调用下列代码
//super.addInterceptors(registry);
}
}
实际拦截处理的配置
package com.example.config.util;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 此处进行 拦截器 的配置操作(如登陆拦截等)----使用拦截器 必须进行配置 springboot配置再config包下
* @author 76519
*
*/
public class BaseInterceptor implements HandlerInterceptor {
// 调用日志进行打印信息处理
private Logger log = LoggerFactory.getLogger(BaseInterceptor.class);
/**
* 请求暂未进入servlet时的操作拦截处理
*/
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
this.log.info("[****BaseInterceptor.preHandle()拦截前****]" +
handlerMethod.getBean().getClass().getSimpleName());
// 放过拦截就返回true 否则拦住就返回false(不继续请求)
return true;
}
/**
* 处理中
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object handler, ModelAndView modelAndView)
throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
this.log.info("[****BaseInterceptor.postHandle()控制器处理中****]" +
handlerMethod.getBean().getClass().getSimpleName());
this.log.info("[****BaseInterceptor.postHandle()****]" + modelAndView);
}
/**
* 处理后
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
this.log.info("[****BaseInterceptor.afterCompletion()控制器处理后****]拦截处理完毕");
}
}
2.4 自定义本地配置文件读取配置
如图所示,我需要读取i18n中的自定义 Messages.properties 配置文件信息
toast.msg=香蕉不拿拿先生爱吃香蕉
①将配置文件信息注册至application.properties中
#读取Messages.properties配置文件的消息信息(多个文件的话 使用","分离)
spring.messages.basename=i18n/Messages
②编写控制器的父类(抽象类)
package com.example.controller.abs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
public abstract class AbsController {
@Autowired
private MessageSource messageSource;
public String getMessage(String msgKey, Object[] args) {
return messageSource.getMessage(msgKey, args, LocaleContextHolder.getLocale());
}
public String getMessage(String msgKey) {
return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale());
}
}
③对于需要实现文件读取的控制器类,进行继承操作
package com.example.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSON;
import com.example.controller.abs.AbsController;
import com.example.service.IUserServiceMaster;
import net.sf.json.JSONObject;
@Controller
@RequestMapping("/demo")
public class DemoController extends AbsController{
private Logger log = LoggerFactory.getLogger(DemoController.class);
@RequestMapping("/show")
@ResponseBody
public String show(){
log.info("----->"+String.valueOf(super.getMessage("toast.msg")));
return "香蕉不拿拿先生爱吃香蕉2";
}
}
④实际测试
INFO com.example.controller.DemoController - ----->香蕉不拿拿先生爱吃香蕉
3、(重点)多个数据源的配置和事务的配置
3.1properties文件的编写
#配置端口号信息
server.port=80
#配置数据源1
datasource.master.driver-class-name: com.mysql.jdbc.Driver
datasource.master.url=jdbc:mysql://localhost:3306/banana1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
datasource.master.username=root
datasource.master.password=root
#配置数据源2
datasource.slaver.driver-class-name: com.mysql.jdbc.Driver
datasource.slaver.url=jdbc:mysql://localhost:3306/banana2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
datasource.slaver.username=root
datasource.slaver.password=root
#配置阿里巴巴数据库连接池
#spring.datasource.type: com.alibaba.druid.pool.DruidDataSource
#最大活跃数
#spring.datasource.maxActive: 20
#初始化数量
#spring.datasource.initialSize: 1
#最大连接等待超时时间
#spring.datasource.maxWait: 60000
#打开PSCache,并且指定每个连接PSCache的大小
#spring.datasource.poolPreparedStatements: true
#spring.datasource.maxPoolPreparedStatementPerConnectionSize: 20
#通过connectionProperties属性来打开mergeSql功能;慢SQL记录
##connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#spring.datasource.minIdle: 1
#spring.datasource.timeBetweenEvictionRunsMillis: 60000
#spring.datasource.minEvictableIdleTimeMillis: 300000
#spring.datasource.validationQuery: select 1 from dual
#spring.datasource.testWhileIdle: true
#spring.datasource.testOnBorrow: false
#spring.datasource.testOnReturn: false
#配置监控统计拦截的filters,去掉后监控界面sql将无法统计,'wall'用于防火墙
#filters: stat, wall, log4j
3.2、实际的数据库布局
两个数据库的表结构一样!
3.3具体的vo类实现
package com.example.vo;
//引用lombok实现自动生成get/set
public class Users {
private Integer userid;
private String username;
private String password;
private String descript;
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDescript() {
return descript;
}
public void setDescript(String descript) {
this.descript = descript;
}
@Override
public String toString() {
return "Users [userid=" + userid + ", username=" + username + ", password=" + password + ", descript="
+ descript + "]";
}
}
本来是打算使用lomback神器进行自动get/set生成的,但发现自己的开发软件并没有安装相关的插件,想想还是算了。
3.4、多个数据源配置的项目目录结构
3.5 、dao层的开发
(两个dao一样的代码,只是包名和类名不同而已,这里就只写一个,写多了累赘)
package com.example.dao.mapper1;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import com.example.vo.Users;
public interface UserMapper1 {
//@Select("select * from users where username = #{userName} and password = #{passWord}")
public Users selectUsersByTerm(String userName,String passWord);
//@Insert("INSERT INTO users(username, password) VALUES(#{userName}, #{passWord})")
public int insertUsers(Users users);
}
3.6 、mapper 的配置
我使用的是mybatis
这是mybatis-config.xml的配置,同理,在classpath:mybatis/mapper1 和 classpath:mybatis/mapper2文件一样
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true" /><!-- 全局映射器启用缓存 -->
<setting name="useGeneratedKeys" value="true" />
<setting name="defaultExecutorType" value="REUSE" />
<!-- 打印sql语句 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
<?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.example.dao.mapper1.UserMapper1">
<select id="selectUsersByTerm" parameterType="java.lang.String" resultType="com.example.vo.Users">
select *
from users
where
username = #{userName} and password = #{passWord}
</select>
<insert id="insertUsers" parameterType="com.example.vo.Users">
INSERT INTO users(username, password) VALUES(#{username}, #{password})
</insert>
</mapper>
两个mapper映射也一样,只是两个mapper的namespace指向不同而已。
3.7、service实现类的代码
(我将两个mapper的同时添加放在一个业务层中实现,要么都成功要么都失败)
package com.example.service.impl;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.dao.mapper1.UserMapper1;
import com.example.dao.mapper2.UserMapper2;
import com.example.service.IUserServiceMaster;
import com.example.vo.Users;
@Service
public class UserServiceMasterImpl implements IUserServiceMaster {
@Resource
private UserMapper1 userMapper1;
@Resource
private UserMapper2 userMapper2;
@Override
public Users selectUsersByTerm(String userName, String passWord) {
return userMapper1.selectUsersByTerm(userName, passWord);
}
@Transactional
@Override
public boolean insertUsers(String userName, String passWord) {
Users users = new Users();
users.setUsername(userName);
users.setPassword(passWord);
int user1 = userMapper1.insertUsers(users);
int a = 10/0;
int user2 = userMapper2.insertUsers(users);
if (user1 > 0 && user2 > 0) {
return true;
}
return false;
}
}
3.8、数据源的配置
master的配置
package com.example.config.datasource;
import java.io.IOException;
import javax.sql.DataSource;
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.alibaba.druid.pool.DruidDataSource;
@Configuration //标注这是配置类
@MapperScan(basePackages=MasterDataSourceConfig.PACKAGE,sqlSessionFactoryRef="masterSqlSessionFactory") //配置扫描的mapper接口和sqlsessionFactory
public class MasterDataSourceConfig {
static final String PACKAGE = "com.example.dao.mapper1"; //master 目录
static final String MAPPER_LOCATION = "classpath:mybatis/mapper1/*/*.xml"; //扫描的 xml 目录
static final String CONFIG_LOCATION = "classpath:mybatis/mapper1/mybatis-config.xml"; //自定义的mybatis config 文件位置
static final String TYPE_ALIASES_PACKAGE = "com.example.vo"; //扫描的 实体类 目录
//引入相关的配置信息 (关联引用application.properties中的配置信息)
@Value("${datasource.master.username}")
private String username;
@Value("${datasource.master.password}")
private String password;
@Value("${datasource.master.url}")
private String url;
@Value("${datasource.master.driver-class-name}")
private String driverclass;
//配置数据源
@Bean(name="masterDataSource")
@Primary //当有多个数据源时,为了避免spring的犹豫不决 采取@Primary注解 告诉spring优先使用
public DataSource masterDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverclass);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
//配置事务管理器
@Bean(name="masterTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager(){
return new DataSourceTransactionManager(masterDataSource());
}
//配置sqlsessionFactory (必须添加 @Qualifier 注解 不然会导致两次不同数据源的数据添加会加入到同一个数据库中)
@Bean(name = "masterSqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource")DataSource masterDataSource) throws Exception{
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MasterDataSourceConfig.MAPPER_LOCATION));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(MasterDataSourceConfig.CONFIG_LOCATION));
sessionFactory.setTypeAliasesPackage(MasterDataSourceConfig.TYPE_ALIASES_PACKAGE);
return sessionFactory.getObject();
}
}
salver的数据源和事务配置
package com.example.config.datasource;
import javax.sql.DataSource;
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.alibaba.druid.pool.DruidDataSource;
@Configuration //标注这是配置类
@MapperScan(basePackages=SlaverDataSourceConfig.PACKAGE,sqlSessionFactoryRef="slaverSqlSessionFactory") //配置扫描的mapper接口和sqlsessionFactory
public class SlaverDataSourceConfig {
static final String PACKAGE = "com.example.dao.mapper2"; //master 目录
static final String MAPPER_LOCATION = "classpath:mybatis/mapper2/*/*.xml"; //扫描的 xml 目录
static final String CONFIG_LOCATION = "classpath:mybatis/mapper2/mybatis-config.xml"; //自定义的mybatis config 文件位置
static final String TYPE_ALIASES_PACKAGE = "com.example.vo"; //扫描的 实体类 目录
//引入相关的配置信息 (关联引用application.properties中的配置信息)
@Value("${datasource.slaver.username}")
private String username;
@Value("${datasource.slaver.password}")
private String password;
@Value("${datasource.slaver.url}")
private String url;
@Value("${datasource.slaver.driver-class-name}")
private String driverclass;
//配置数据源
@Bean(name="slaverDataSource")
public DataSource slaverDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverclass);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
//配置事务管理器
@Bean(name="slaverTransactionManager")
public DataSourceTransactionManager masterTransactionManager(){
return new DataSourceTransactionManager(slaverDataSource());
}
//配置sqlsessionFactory(必须添加 @Qualifier 注解 不然会导致两次不同数据源的数据添加会加入到同一个数据库中)
@Bean(name = "slaverSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("slaverDataSource")DataSource slaverDataSource) throws Exception{
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(slaverDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SlaverDataSourceConfig.MAPPER_LOCATION));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(SlaverDataSourceConfig.CONFIG_LOCATION));
sessionFactory.setTypeAliasesPackage(SlaverDataSourceConfig.TYPE_ALIASES_PACKAGE);
return sessionFactory.getObject();
}
}
这两个java类的配置表示踩了不少坑,对此做下总结,方便准备踏坑的童鞋看看,少走弯路(对于我没有总结到的,还望大家多多指出):
坑一、最开始配置数据源时,我没有在其中一个数据源的配置上添加 @Primary 注解,导致spring根本不知道使用哪种事务/数据源进行数据操作,如果对spring的@Primary注解不了解的,可以去看看别的大佬的资料。(@Primary只是当一个接口存在多个实现类时,告诉使用者犹豫不决时,优先考虑哪一个实现类);
坑二、在配置sqlSessionFactory的时候,我没有在方法中具体指明哪一个datasource,导致问题是我添加数据的时候,使得两个mapper的操作都执行到了同一个数据库的同一张表中,对此找了好久的bug,最后才知道没有指明具体的datasource。所以后台添加了 @Qualifier("masterDataSource")DataSource masterDataSource 和 @Qualifier("slaverDataSource")DataSource slaverDataSource
3.9、控制层
package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.service.IUserServiceMaster;
@Controller
@RequestMapping("/demo")
public class DemoController {
@Autowired
private IUserServiceMaster userServiceMasterImpl;
@RequestMapping("/demo")
@ResponseBody
public String index(){
//int a = 10/0;
return "这是一根香蕉";
}
@RequestMapping("/test")
@ResponseBody
public String test(){
userServiceMasterImpl.insertUsers("xiangjiao", "bunana111");
return "香蕉不拿拿先生爱吃香蕉1";
}
}