微服務下項目絕對不是傳統的單體數據庫設計,此時就涉及到了多數據源
項目案例代碼上傳到git
https://gitee.com/gangye/springboot_more_databases
首先創建一個SpringBoot項目,具體項目結構
引入maven依賴
<?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>
<groupId>com.gangye</groupId>
<artifactId>springboot_much_database</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
在application.properties文件中配置數據源的配置,以及mybatis的配置
server.port=8089
# master database
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/master_test_database?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.master.username=root
spring.datasource.master.password=ok
# slave database
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/slave_test_database?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.slave.username=root
spring.datasource.slave.password=ok
#mybatis的相關配置
#mybatis.mapper-locations=classpath:mapper/master/*.xml,classpath:mapper/slave/*.xml
mybatis.config-location=classpath:mybatis-config.xml
其中將url改成jdbc-url,不改的話啓動後會連接數據庫報錯java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
有三種方法可以解決這個錯誤:
具體參考:https://blog.csdn.net/MrLi_IT/article/details/80909078
由於使用了多數據源,不是單一數據源,所以就不能使用默認的框架內的配置,需要自己寫配置類,當有一個數據庫時,創建一個數據源配置類
package com.gangye.dataSourceConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Classname DataSourceMasterConfig
* @Description 數據庫爲master_test_database的數據源配置類,由於必須有一個作爲主庫,所以主庫時必須加上@Primary註解
* @Date 2020/5/26 16:54
* @Created by gangye
*/
@Configuration
@MapperScan(basePackages = "com.gangye.mapper.mapper1",sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class DataSourceMasterConfig {
//mapper掃描xml文件的路徑
static final String MAPPER_LOCATION = "classpath:mapper/master/*.xml";
/**
* 配置數據源
* primary是設置優先,因爲有多個數據源,在沒有明確指定用哪個的情況下,會用帶有primary的,這個註解必須有一個數據源要添加
*/
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
@Primary
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置SqlSessionFactory
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception{
final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
return sqlSessionFactoryBean.getObject();
}
/**
* 配置事務管理器
* @param dataSource
* @return
*/
@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
/**
* 結合類註解MapperScan配置MapperScannerConfigurer
* @param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "primarySqlSessionTemplate")
@Primary
public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
爲第二個數據庫創建配置類
package com.gangye.dataSourceConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Classname DataSourceSecondConfig
* @Description 數據庫爲slave_test_database的數據源配置類,其餘的數據庫類似,非主數據庫不需加上@Primary註解
* @Date 2020/5/27 8:44
* @Created by gangye
*/
@Configuration
@MapperScan(basePackages = "com.gangye.mapper.mapper2",sqlSessionTemplateRef = "secondSqlSessionTemplate")
public class DataSourceSecondConfig {
//mapper掃描xml文件的路徑
final static String SECOND_DATASOURCE_MAPPER_LOCATION = "classpath:mapper/slave/*.xml";
/**
* 配置數據源
* @return
*/
@Bean(name = "secondDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource secondDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置SqlSessionFactory
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "secondSqlSessionFactory")
public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception{
final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SECOND_DATASOURCE_MAPPER_LOCATION));
return sqlSessionFactoryBean.getObject();
}
/**
* 配置事務管理器
* @param dataSource
* @return
*/
@Bean(name = "secondTransactionManager")
public DataSourceTransactionManager secondTransactionManager(@Qualifier("secondDataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
/**
* 結合類註解MapperScan配置MapperScannerConfigurer
* @param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "secondSqlSessionTemplate")
public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
後續工作類似單數據源的操作,創建實體類,mapper,service以及controller,在使用事務的時候對應的事務使用對應的註解即可
創建實體類User
package com.gangye.entity;
import lombok.Data;
/**
* @Classname User
* @Description 對應master_test_database的user表的實體類
* @Date 2020/5/26 16:33
* @Created by gangye
*/
@Data
public class User {
private Integer id;
private String name;
private String description;
}
user的mapper層,亦或者dao層
package com.gangye.mapper.mapper1;
import com.gangye.entity.User;
import java.util.List;
/**
* @Classname UserMapper
* @Description user的dao層
* @Date 2020/5/26 16:40
* @Created by gangye
*/
public interface UserMapper {
User findById(Integer id);
List<User> showAll();
void insert(User user);
void update(User user);
}
對應的mapper.xml的文件
<?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.gangye.mapper.mapper1.UserMapper">
<resultMap id="BaseResultMap" type="com.gangye.entity.User">
<result column="id" property="id" />
<result column="name" property="name" />
<result column="description" property="description" />
</resultMap>
<sql id="Base_Column_List">
id, name, description
</sql>
<select id="findById" resultMap="BaseResultMap" parameterType="java.lang.Integer">
select <include refid="Base_Column_List"/> from user where id = #{id}
</select>
<select id="showAll" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/> from user
</select>
<insert id="insert" parameterType="com.gangye.entity.User">
insert into user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
<if test="description !=null">
description,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
</if>
<if test="description != null">
#{description,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="update" parameterType="com.gangye.entity.User" >
update user
<set>
<if test="name != null and name != ''">name = #{name},</if>
<if test="description != null and description != ''">description = #{description},</if>
</set>
where id = #{id}
</update>
</mapper>
user的相關service層(省略了UserService接口),此處可以看到對應的事務註解使用的是@Transactional(transactionManager = "primaryTransactionManager")
package com.gangye.service.impl;
import com.gangye.entity.User;
import com.gangye.mapper.mapper1.UserMapper;
import com.gangye.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @Classname UserServiceImpl
* @Description userSercice的接口實現類
* @Date 2020/5/27 9:49
* @Created by gangye
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserByPrimarykey(Integer id) {
return userMapper.findById(id);
}
@Override
public List<User> showAllUsers() {
return userMapper.showAll();
}
@Override
@Transactional(transactionManager = "primaryTransactionManager")
public void insertOneUser(User user) {
userMapper.insert(user);
}
@Override
@Transactional(transactionManager = "primaryTransactionManager")
public void updateUserInfo(User user) {
userMapper.update(user);
}
}
最後控制路由
package com.gangye.controller;
import com.gangye.entity.User;
import com.gangye.service.UserService;
import com.gangye.tools.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Classname UserController
* @Description user相關的控制路由
* @Date 2020/5/27 9:51
* @Created by gangye
*/
@RestController
@RequestMapping(value = "/userController")
public class UserController {
@Autowired
private UserService userService;
//展示所有用戶
@PostMapping("/showAllUsers")
public Response showAllUsers(){
List<User> userList = userService.showAllUsers();
Response response = Response.newResponse();
return response.setData(userList);
}
//根據id查詢用戶信息
@PostMapping("/showUserInfo")
public Response showUserInfo(Integer id){
Response response = Response.newResponse();
User userInfo = userService.getUserByPrimarykey(id);
return response.setData(userInfo);
}
//更改用戶信息
@PostMapping("/updateUserInfo")
public Response updateUserInfo(@RequestBody User user){
Response response = Response.newResponse();
if (user.getId()!=null && ! "".equals(user.getId())){
User tempUser = userService.getUserByPrimarykey(user.getId());
if (tempUser==null){
return response.setCodeAndMessage(9999,"用戶不存在!");
}
userService.updateUserInfo(user);
return response.OK();
}
return response.setCodeAndMessage(9999,"更改信息失敗!");
}
//新增用戶
@PostMapping("/addUser")
public Response addUser(@RequestBody User userInfo){
Response response = Response.newResponse();
userService.insertOneUser(userInfo);
return response.OK();
}
}