前言 說實話,這章本來不打算講的,因為配置多數據源的網上有很多類似的教程。但是最近因為項目要用到分庫分表,所以讓我研究一下看怎麼實現。我想著上一篇博客講了多環境的配置,不同的環境調用不同的資料庫,那接下來就將一個環境用到多個庫也就講了。所以才有了這篇文章。 我們先來看一下今天項目的項目結構,在上篇博 ...
前言
說實話,這章本來不打算講的,因為配置多數據源的網上有很多類似的教程。但是最近因為項目要用到分庫分表,所以讓我研究一下看怎麼實現。我想著上一篇博客講了多環境的配置,不同的環境調用不同的資料庫,那接下來就將一個環境用到多個庫也就講了。所以才有了這篇文章。
我們先來看一下今天項目的項目結構,在上篇博客的基礎上進行了一定的增改,主要是增加了一個 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
後續加油♡
歡迎大家關註個人公眾號 "程式員愛酸奶"
分享各種學習資料,包含java,linux,大數據等。資料包含視頻文檔以及源碼,同時分享本人及投遞的優質技術博文。
如果大家喜歡記得關註和分享喲❤