
前言
說實話,這章本來不打算講的,因為配置多資料源的網上有很多類似的教程。但是最近因為項目要用到分庫分表,是以讓我研究一下看怎麼實作。我想着上一篇部落格講了多環境的配置,不同的環境調用不同的資料庫,那接下來就将一個環境用到多個庫也就講了。是以才有了這篇文章。
我們先來看一下今天項目的項目結構,在上篇部落格的基礎上進行了一定的增改,主要是增加了一個 config 檔案,在dao 中分了兩個子包mapper1 和mapper2 将原先的UserMapper 移入到了 mapper1 中。好了,開始正文
多資料源配置
背景
在這之前,還是先說一下為什麼會存在多資料源。如果項目小的話,當然是所有的資料以及邏輯處理都操作同一個庫。但是當業務量大的話,就會考慮到分庫了。比如我會将日志入庫資料存放到單獨的資料庫。或者使用者權限資訊單獨的一個庫存放。這種如果隻是簡單的分庫,一個項目中就用到2~4 個資料庫的話,這種多資料源配置就有意義啦。在配置檔案中配置好這幾個資料源,都有唯一辨別。項目在啟動加載的時候都進行初始化,然後在調用的時候,想用哪個庫就哪個資料源的連接配接執行個體就好了。
如果不整合 mybatis 的話,直接使用使用spring 自帶的jdbcTemplate ,那配置多資料源,以及使用都比較簡單,但是整合 mybatis 的話,就相對複雜點。我們一步一步來講解。
修改配置檔案
打開application-dev.yml 檔案,添加資料源。
#開發環境
spring:
# 資料源配置
datasource:
one:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.252.53:3306/zlflovemm?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL
username: root
password: 123456
max-idle: 10
max-wait: 10000
min-idle: 5
initial-size: 5
two:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.252.53:3306/zlfdb?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL
username: root
password: 123456
max-idle: 10
max-wait: 10000
min-idle: 5
initial-size: 5
複制
這裡需要注意的是如果使用的是springboot 2.0 以上的,那麼注意是 driver-class-name 和
jdbc-url 而不是driverClassName和url.這裡是一個坑,提醒大家一下。
配置資料源
接下來就需要我們手動的加載什麼什麼資料源了,我們在config中建立 DataSourcesConfig 類
@Configuration
public class DataSourcesConfig {
@Bean(name="dbOne")
@ConfigurationProperties(prefix = "spring.datasource.one")
@Primary
DataSource dbOne(){
return DataSourceBuilder.create().build();
}
@Bean(name="dbTwo")
@ConfigurationProperties(prefix = "spring.datasource.two")
DataSource dbTwo(){
return DataSourceBuilder.create().build();
}
}
複制
這裡定義了兩個資料源的DataSource。分别是我們在配置檔案中配置的one 和two 。注解@Primary 表示預設使用的資料源。
MyBatisConfigOne 類
@Configuration
@MapperScan(basePackages = "com.quellan.zlflovemm.dao.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
@Resource(name = "dbOne")
DataSource dbOne;
@Bean
@Primary
SqlSessionFactory sqlSessionFactory1()throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dbOne);
return bean.getObject();
}
@Bean
@Primary
SqlSessionTemplate sqlSessionTemplate1() throws Exception{
return new SqlSessionTemplate(sqlSessionFactory1());
}
}
複制
MyBatisConfigTwo 類
@Configuration
@MapperScan(basePackages = "com.quellan.zlflovemm.dao.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
@Resource(name = "dbTwo")
DataSource dbTwo;
@Bean
SqlSessionFactory sqlSessionFactory2()throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dbTwo);
return bean.getObject();
}
@Bean
SqlSessionTemplate sqlSessionTemplate2()throws Exception {
return new SqlSessionTemplate(sqlSessionFactory2());
}
}
複制
注意兩個檔案的差別:
dao 層
在dao 層建立了兩個包mapper1 和mapper2 .包裡面的UserMapper類的内容是完全一樣,放在不同的包中隻是區分使用哪個資料源。和昨天是一樣的。
public interface UserMapper {
@Select("select id,username as userName,password,email,role_code as roleCode,gmt_create as gmtCreate,gmt_update as gmtUpdate,nickname as nickName,user_create as userCreate from sys_user")
List<UserEntry> findUserList();
@Insert({"insert into sys_user(username,password,email) values('${user.userName}','${user.password}','${user.email}')"})
int add(@Param("user") UserEntry user);
@Delete("delete from sys_user where id = #{id}")
int delete(int id);
}
複制
service 層
UserService接口
public interface UserService {
List<UserEntry> findUserList();
int addUser(String userName,String password,String email);
int deleteUser(int id);
List<UserEntry> findUserList2();
int addUser2(String userName,String password,String email);
int deleteUser2(int id);
}
複制
UserServiceImpl類:
@Service
public class UserServiceImpl implements UserService {
@Autowired
protected UserMapper userMapper;
@Autowired
protected UserMapper2 userMapper2;
@Override
public List<UserEntry> findUserList() {
return userMapper.findUserList();
}
@Override
public int addUser(String userName, String password, String email) {
UserEntry user=new UserEntry();
user.setUserName(userName);
user.setPassword(password);
user.setEmail(email);
return userMapper.add(user);
}
@Override
public int deleteUser(int id) {
return userMapper.delete(id);
}
@Override
public List<UserEntry> findUserList2() {
return userMapper2.findUserList();
}
@Override
public int addUser2(String userName, String password, String email) {
UserEntry user=new UserEntry();
user.setUserName(userName);
user.setPassword(password);
user.setEmail(email);
return userMapper2.add(user);
}
@Override
public int deleteUser2(int id) {
return userMapper2.delete(id);
}
}
複制
controller 層
userController
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/list",method = RequestMethod.GET)
public List<UserEntry> findUserList(){
return userService.findUserList();
}
@RequestMapping(value = "/add",method = RequestMethod.GET)
public String addUser(@RequestParam(value = "userName")String uaserName,@RequestParam(value = "password")String password,@RequestParam(value = "email")String email){
int falg=userService.addUser(uaserName,password,email);
if(falg>0){
return "success";
}
return "error";
}
@RequestMapping(value = "/delete",method = RequestMethod.GET)
public String deleteUser(@RequestParam(value = "id")int id){
if(userService.deleteUser(id)>0){
return "success";
}
return "error";
}
@RequestMapping(value = "/list2",method = RequestMethod.GET)
public List<UserEntry> findUserList2(){
return userService.findUserList2();
}
@RequestMapping(value = "/add2",method = RequestMethod.GET)
public String addUser2(@RequestParam(value = "userName")String uaserName,@RequestParam(value = "password")String password,@RequestParam(value = "email")String email){
int falg= userService.addUser2(uaserName,password,email);
if(falg>0){
return "success";
}
return "error";
}
@RequestMapping(value = "/delete2",method = RequestMethod.GET)
public String deleteUser2(@RequestParam(value = "id")int id){
if(userService.deleteUser2(id)>0){
return "success";
}
return "error";
}
}
複制
測試
可以看到是從不同的庫中調出來的。這樣就說明我們springboot配置多資料源整合mybatis 已經成功了。其實最主要就是config 包下的那三個配置類。其他的都是常見的業務邏輯,是以後面我就沒有怎麼講了,代碼會同步到github 上,想要實踐的可以拿源碼下來實踐。
到此我們springboot整合mybatis 多資料源已經配置好了,但是我們配置下來可以發現,我們如果想要配置幾個資料源就得在 dao 層建立多少個子包用來區分。那如果我們資料量足夠大,要分庫分表而不是幾個庫呢?
分庫分表
背景
其實分庫分表和多資料源是一樣的,隻不過是資料源更多了,多到在配置中配置所有的連接配接顯得很臃腫,是以不得不另覓它法。分庫分表就是 在項目中配置連接配接主庫的連接配接,從主庫中讀取各個分庫的連接配接,然後進行動态的加載,那個接口想調用那個分庫就加載這個分庫的連接配接。
我現在項目做的由于不用整合mybatis 直接使用jdbcTemplate ,是以實作起來不是很麻煩。
思路
主要就兩個類;
GetDynamicJdbcTemplate類:手動的建立連接配接。
/**
* @ClassName GetDynamicJdbcTemplate
* @Description 擷取動态的jdbcTemplate
* @Author zhulinfeng
* @Date 2019/9/20 14:35
* @Version 1.0
*/
public class GetDynamicJdbcTemplate {
private String driverClassName;
private String url;
private String dbUsername;
private String dbPassword;
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public GetDynamicJdbcTemplate(String driverClassName, String url, String dbUsername, String dbPassword){
this.driverClassName=driverClassName;
this.url=url;
this.dbUsername=dbUsername;
this.dbPassword=dbPassword;
this.jdbcTemplate=new JdbcTemplate(getDataSource());
}
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
return dataSource;
}
}
複制
GetJdbcTemplateMap類在項目啟動的時候,會讀取主庫中的配置,将所有分庫的連接配接都建立好放到map中。我們是按照地市分表的,接口在調用的時候根據前端傳過來的地市就可以知道使用哪個資料庫連接配接了。
@Component
@Slf4j
public class GetJdbcTemplateMap implements ApplicationRunner {
@Autowired
@Qualifier("baseTemplate")
private JdbcTemplate jdbcTemplate;
public static Map<String,JdbcTemplate> JdbcTemplateMap=new HashMap<>();
@Override
public void run(ApplicationArguments args) throws Exception {
String sql="CALL proc_baseinfo_cfg_dbsetting_query()";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
if(list!=null && !list.isEmpty()){
insertMap(list);
}
}
private void insertMap(List<Map<String, Object>> list){
for(Map<String, Object> map :list){
String url="jdbc:mysql://"+map.get("serverip")+":"+map.get("dbport")+"/"+map.get("dbname")+"?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
log.info(url);
String dbUsername= map.get("user").toString();
String dbPassword= map.get("password").toString();
GetDynamicJdbcTemplate getDynamicJdbcTemplate=new GetDynamicJdbcTemplate(ConstantClass.DRIVERCLASSNAME,url,dbUsername,dbPassword);
JdbcTemplate jdbcTemplate=getDynamicJdbcTemplate.getJdbcTemplate();
JdbcTemplateMap.put(map.get("cityid").toString(),jdbcTemplate);
}
}
}
複制
在接口中調用也很友善。
但是上面講的隻适合我們自己特有的業務,并且也沒有整合mybatis ,是以我就沒有寫在我自己的項目中,這裡提供出來是給大家一個思路。
番外
也算是寫完了這篇,感覺寫的不是很好,但是有不知道怎麼修改,暫時就先這樣吧,後續有思路了再進行修改。又問問我為什麼不先整合Thymeleaf 弄出頁面來。之是以沒有弄,是因為我想後期做前後端分離都是以接口的形式調用。是以想先将後端的部分都搭建好,再來整合前端的。
好了,就說這麼多啦,今天項目的代碼也同步到github 上啦。
github位址:https://github.com/QuellanAn/zlflovemm