#依赖
# 使用jdbc依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
# 使用mybatis依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
数据库配置
- url地址
local...
后是库名称 ```yaml
spring: datasource: username: root password: root
# ?serverTimezone=UTC 解决时区的报错,后面2个配置处理字符集
#写在url里面的无法加时区数字,如UTC+8是错误的
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver #驱动名称
也可以把时区写最上面,表示对以下所有数据库都失效
spring: time-zone: GMT+8 datasource:
注意jackson也有time-zone这个配置,通过jackson配时区时为这样:
spring: jackson: time-zone: GMT+8 datasource:
<a name="wlAJN"></a>
## 使用连接池时的配置
- springboot-jdbc的依赖集成了hikari,配置下配置文件即可直接使用
```yaml
datasource:
url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
initialization-mode: always
continue-on-error: true
schema:
- "classpath:db/schema.sql"
data:
- "classpath:db/data.sql"
hikari:
minimum-idle: 5
connection-test-query: SELECT 1 FROM DUAL #测试连接通不通的语句
maximum-pool-size: 20
auto-commit: true
idle-timeout: 30000
pool-name: SpringBootDemoHikariCP
max-lifetime: 60000
connection-timeout: 30000
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver #驱动名称
username: root
password: root
druid:
# 配置初始化大小、最小、最大
initial-size: 5
minIdle: 10
max-active: 20
# 配置获取连接等待超时的时间(单位:毫秒)
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 2000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 600000
max-evictable-idle-time-millis: 900000
# 用来测试连接是否可用的SQL语句,默认值每种数据库都不相同,这是mysql
validationQuery: select 1
# 应用向连接池申请连接,并且testOnBorrow为false时,连接池将会判断连接是否处于空闲状态,如果是,则验证这条连接是否可用
testWhileIdle: true
# 如果为true,默认是false,应用向连接池申请连接时,连接池会判断这条连接是否是可用的
testOnBorrow: false
# 如果为true(默认false),当应用使用完连接,连接池回收连接的时候会判断该连接是否还可用
testOnReturn: false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle
poolPreparedStatements: true
# 要启用PSCache,必须配置大于0,当大于0时, poolPreparedStatements自动触发修改为true,
# 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,
# 可以把这个数值配置大一些,比如说100
maxOpenPreparedStatements: 20
# 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作
keepAlive: true
# Spring 监控,利用aop 对指定接口的执行时间,jdbc数进行记录
aop-patterns: "com.springboot.template.dao.*"
########### 启用内置过滤器(第一个 stat必须,否则监控不到SQL)##########
filters: stat,log4j2
# 自己配置监控统计拦截的filter,这里配置了wall则filters就不要有wall了,否则还是不允许批量操作
filter:
wall:
config:
multiStatementAllow: true #true开启批量更新
# 开启druiddatasource的状态监控
stat:
enabled: true
db-type: mysql
# 开启慢sql监控,超过2s 就认为是慢sql,记录到日志中
log-slow-sql: true
slow-sql-millis: 2000
# 日志监控,使用slf4j 进行日志输出
slf4j:
enabled: true
statement-log-error-enabled: true
statement-create-after-log-enabled: false
statement-close-after-log-enabled: false
result-set-open-after-log-enabled: false
result-set-close-after-log-enabled: false
########## 配置WebStatFilter,用于采集web关联监控的数据 ##########
web-stat-filter:
enabled: true # 启动 StatFilter
url-pattern: /* # 过滤所有url
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除一些不必要的url
session-stat-enable: true # 开启session统计功能
session-stat-max-count: 1000 # session的最大个数,默认100
########## 配置StatViewServlet(监控页面),用于展示Druid的统计信息 ##########
stat-view-servlet:
enabled: true # 启用StatViewServlet
url-pattern: /druid/* # 访问内置监控页面的路径,内置监控页面的首页是/druid/index.html
reset-enable: false # 不允许清空统计数据,重新计算
login-username: root # 配置监控页面访问密码
login-password: 123
allow: 127.0.0.1 # 允许访问的地址,如果allow没有配置或者为空,则允许所有访问
deny: # 拒绝访问的地址,deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝
JDBC
- 以往我们要使用连接池获取连接,需要创建
配置信息类
,通过配置信息类得到连接池对象
。再通过连接池对象得到DataSource
对象 - 现在我们把配置信息写在配置文件中,自动注入会根据配置文件自动注入得到
DataSource
对象
dataSource.getClass()
得到的信息是class com.zaxxer.hikari.HikariDataSource
- Spring Boot 2.2.5 默认使用HikariDataSource 数据源,所以我们也应该使用hikari来获取jdbc ```java
@SpringBootTest class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//查看ds对象的类,得到ds对象连接池类型
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
//关闭连接
connection.close();
}
}
<a name="W2cAy"></a>
# JdbcTemplate
- 在 Spring 中操作数据库,可以使用 Spring 提供的 JdbcTemplate 对象,JdbcTemplate 类提供了很多便利的方法,比如把数据库数据转变成基本数据类型或对象,执行自定义的 SQL 语句,提供了自定义的数据错误处理等,JdbcTemplate 使用示例如下:
- Spring 的 JdbcTemplate 是对 JDBC API 的封装
- JdbcTemplate 是线程安全的;
- 自动完成资源的创建和释放工作;
```java
@Autowired
private JdbcTemplate jdbcTemplate;
// 新增
@GetMapping("save")
public String save() {
String sql = "INSERT INTO USER (USER_NAME,PASS_WORD) VALUES ('laowang','123')";
int rows = jdbcTemplate.update(sql);
return "执行成功,影响" + rows + "行";
}
// 删除
@GetMapping("del")
public String del(int id) {
int rows= jdbcTemplate.update("DELETE FROM USER WHERE ID = ?",id);
return "执行成功,影响" + rows + "行";
}
// 查询
@GetMapping("getMapById")
public Map getMapById(Integer id) {
String sql = "SELECT * FROM USER WHERE ID = ?";
Map map= jdbcTemplate.queryForMap(sql,id);
return map;
}
Mybatis
注解版
- 直接在mapper接口上增加
@Mapper
即可注册为mapper接口 .- 或者使用
@MapperScan(?)
在boot主启动类上,参数为mapper接口所在的包,这样mapper接口无需再每一个都加@Mapper
- 或者使用
- 使用依赖注入获取mapper接口以使用sql方法
- springboot中的mybatis注解版无需在配置文件中配置dao-接口文件位置,只要能被扫描到即可用,即上面的注册操作 ```java @Mapper public interface sourceMapper { // 获取所有部门信息 @Select(“select * from score”) List getDepartments(); }
class Test(){ @Autowired sourceMapper sourcexMapper;
main(){
List<source> ss= sourcexMapper.getDepartments();
}
}
<a name="linVr"></a>
## xml版
- xml版跟注解版几乎一样
- **springboot里xml写在resource下时无需配置扫描,写在java下时需要配置**
- 这就相当于`mybatis.xml`配置mapper-xml文件位置
- 写在src/java下的xml**必须指定具体路径,无法使用**`****/*.xml**`最多就`*.xml` 路径为java下的包路径,如`com/rao/www/mapper.*.xml`
```properties
# classpath即表示resource目录
#这里xml是直接写在resource下所以路径为: *.xml 现在表示当前路径的所以xml 如果想包括当前路径
#及其子路径,则为**/*.xml
#当然也可以直接写具体的路径,如 mapper/UserMapper.xml
mybatis.mapper-locations=classpath:*.xml
#yml写法:
mybatis:
mapper-locations: classpath:*.xml
资源过滤
跟ssm里差不多. 不过这里写了resources的资源配置(没啥用就是,因为spring和boot中对于resource下的xml可以自动识别)
springboot默认集成了logging日志,通过如下配置可自动输出sql的执行记录
- 试了下只能在propetes中配置有用,拿到yaml中无效
level后为mapper接口路径,一定得是包才有效,如果写在src/java下,则
logging.level=debug
是错的logging.level.com.boot.mapper=debug
事务
你的程序是否支持事务首先取决于数据库,数据库不支持使用了事务也不会生效
spring事务使用
Spring 框架中,事务管理相关最重要的 3 个接口如下:
**PlatformTransactionManager**
: (平台)事务管理器,Spring 事务策略的核心。- 提供了3个方法,获取,提交,回滚事务。见下面代码块
**TransactionDefinition**
:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。即事务属性**TransactionStatus**
: 事务运行状态。事务管理器获取事务返回的就是该类对象**TransactionDefinition **
和**TransactionStatus**
这两个接口可以看作是事务的描述。**PlatformTransactionManager**
会根据**TransactionDefinition**
的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而**TransactionStatus**
接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等
编程式事务管理
较少使用,需要手动管理事务比较麻烦,但是对于你理解 Spring 事务管理原理有帮助。
借助
TransactionTemplate
或者TransactionManager
手动管理事务@Autowired private TransactionTemplate transactionTemplate; public void testTransaction() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { try { // .... 业务代码 } catch (Exception e){ //回滚 transactionStatus.setRollbackOnly(); } } }); }
```java @Autowired private PlatformTransactionManager transactionManager;
public void testTransaction() { //获得事务 TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // …. transactionManager.commit(status); //提交事务 } catch (Exception e) { transactionManager.rollback(status); //回滚 } }
<a name="wRyZl"></a>
### 声明式事务管理
- **推荐使用(代码侵入性最小),实际是通过 AOP 实现**
- 在类或者方法上使用`@Transactional `。**不建议在类上使用事务**,事务层层调用很耗费资源
- 当把@Transactional 注解放在类上时,表示所有该类的 `**public **`方法都配置相同的事务属性信息。
- 如果放接口上:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
- 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,**方法的事务会覆盖类的事务配置信息。 如下是**`**Transactional**`失效的原因
<a name="ChUnY"></a>
#### 事务失效的情况
事务失效即事务未回滚成功,spring事务本质还是利用spring aop,因此会影响动态代理的因素也会影响spring事务, 非公开的方法,final方法,static方法无法通过动态代理重写,因此也无法进行spring事务
1. **方法非公开**:Transactional注解应用在非`public`,`static`,`final`修饰的方法上,Transactional将会失效.(原因见[链接](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486483&idx=2&sn=77be488e206186803531ea5d7164ec53&chksm=cea243d8f9d5cacecaa5c5daae4cde4c697b9b5b21f96dfc6cce428cfcb62b88b3970c26b9c2&token=816772476&lang=zh_CN#rd))。注意只是失效不会报错
1. **同一个类中未有事务方法调用有事务方法**:A再调如果用**本类**的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务将失效。
- 如果普通方法需要调当前类的事务方法,1:除了把事务方法移动到其他服务,2:还可以选择在当前服务注入当前服务然后调用事务方法(spring ioc三级缓存避免了这种情况的循环依赖)3:或者使用如下方法获取代理对象然后调用
```java
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
- 方法嵌套异常捕获:事务中方法A捕获B方法的异常,事务不会正常回滚
- 错误的注解参数:
- 异常捕获的类型不正确:在业务方法中一般不需要手动catch异常,如果非要catch一定要抛出
throw new RuntimeException()
,让@Transactional能够捕获,或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class)
。这样才能执行异常回滚 - 错误配置传播行为也会导致当前事务失效
- 异常捕获的类型不正确:在业务方法中一般不需要手动catch异常,如果非要catch一定要抛出
- 抛出的异常catch处理了又未再手动抛出, 则事务失效。即开发者处理了这个异常,则spring捕获不到该异常
- 事务方法所在类未被spring管理
- 多线程时使用同一个事务方法,这些事务不是同一个事务,因此一个线程里的方法回滚不会影响其他他线程(spring的事务是通过数据库连接来实现,只有同一个连接的相同方法才是一个事务)
- 未开启事务:这种情况一般出现在非springboot的项目中,因为springboot的
DataSourceTransactionManagerAutoConfiguration
默认配置时已经开启了事务 - 还有一种会导致失效,数据库不支持事务,这种一般不会发生,使用前就设置号了
事务属性
timeout
:事务的超时时间,默认值为 -1,即无。如果超过该时间限制但事务还没有完成,则自动回滚事务。**rollbackFor**
回滚规则:在@Transactional
注解中如果不配置rollbackFor
属性,那么事务默认只会在遇到**RuntimeException**
的时候才会回滚,加上rollbackFor=Exception.class
,可以让事务在遇到非运行时异常时也回滚。也可以指定异常**isolation**
设置隔离级别Isolation._DEFAULT/READ_UNCOMMITTED/READ_COMMITTED/SERIALIZABLE_
_DEFAULT_
表示采用数据库默认的隔离级别,**isolation**
的默认值为_**DEFAULT**_
**propagation**
代表事务的传播行为,默认值为**Propagation.REQUIRED**
,如果错误的配置如下三种,可能也会导致失效- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
readOnly
指定事务是否为只读事务,默认值为 false;只读事务不涉及数据的修改,数据库会提供一些针对多条查询命令的优化传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
- 如: 我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了
- 默认值为 Propagation.REQUIRED,如
//一个方法回滚,则另外一个方法也回滚 Class A { @Transactional(propagation=propagation.PROPAGATION_REQUIRED) public void aMethod { //do something B b = new B(); b.bMethod(); } } Class B { @Transactional(propagation=propagation.PROPAGATION_REQUIRED) public void bMethod { //do something } }
- Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。2个事务互相影响( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
- Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
- Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
- Propagation.REQUIRES_NEW:存在不存在都重新创建一个新的事务,如果当前存在事务,暂停当前的事务。事务相互独立,互不干扰。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )。但是b出现异常回滚,a也会回滚
- Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
- Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
- Propagation.NESTED :当前无事务时,和
Propagation.REQUIRED
效果一样。如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;嵌套事务不影响外部事务,但是外部事务影响嵌套事务- 如a调b,b出错b回滚a不滚。但是a出错a,b都回滚
对于希望子方法事务不影响外部事务方法,也可以采用手动catch子方法事务的异常,这样异常就不会层层向上传递
@Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); try { roleService.doOtherThing(); } catch (Exception e) { log.error(e.getMessage(), e); } } }
多数据源
要实现多数据源其实很简单。最主要的类就是
AbstractRoutingDataSource
,该类内部实现了数据源的切换。我们要做的就是传入一组数据源与数据源对应的键,并重写**AbstractRoutingDataSource**
的**determineCurrentLookupKey**
(该方法是获取一个键,然后抽象路由数据源类会根据键去那组数据源里找对应数据源使用)-
禁用自动配置
@SpringBootApplication // 启动自动配置,但排除指定的自动配置: @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public class Application { ... }
多数据源的配置
具体的配置完全不是固定的,因为我们只要能实现数据源的注入即可
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver master: url: jdbc:mysql://127.0.0.1:3307/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true username: root password: #也可以针对具体的如驱动,数据库类型具体再进行配置,不配置则采用默认配置 slave: url: jdbc:mysql://127.0.0.1:3308/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true username: root password:
手动创建数据库操作相关的bean
手动创建DataSouce,不使用自动创建(自动创建时只能有一个默认配置)
- 下面是一主一从时的配置
上面说过,多数据源切换最重要的类就是
AbstractRoutingDataSource
,主从动态配置就是将多个数据源以及对应键存储到map中传给AbstractRoutingDataSource
@Configuration @MapperScan(basePackages = "com.xjt.proxy.mapper", sqlSessionTemplateRef = "sqlTemplate") //这里不知道是不是将所有的mapper和sqlTemplate纳入管理的意思 public class DataSourceConfig { // 主库,这里采用的是bean工厂创建bean的方法,也可以自己创建DataSourceProperties的bean,然后再将该配置对象作为参数传给datasource的bean进行创建 @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDb() { return DruidDataSourceBuilder.create().build(); } /** * 从库 */ //ConfigurationProperties 从库有该注解表名从库不是必须的,只有配置了从库时才会进行创建 @Bean @ConditionalOnProperty(prefix = "spring.datasource", name = "slave", matchIfMissing = true) @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDb() { return DruidDataSourceBuilder.create().build(); } /** * 主从动态配置 */ @Bean public DynamicDataSource dynamicDb(@Qualifier("masterDb") DataSource masterDataSource, @Autowired(required = false) @Qualifier("slaveDb") DataSource slaveDataSource) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DynamicDataSourceEnum.MASTER.getDataSourceName(), masterDataSource); if (slaveDataSource != null) { targetDataSources.put(DynamicDataSourceEnum.SLAVE.getDataSourceName(), slaveDataSource); } //设置数据源和对应的键 dynamicDataSource.setTargetDataSources(targetDataSources); //设置默认的数据源 dynamicDataSource.setDefaultTargetDataSource(masterDataSource); return dynamicDataSource; } //Qualifier为动态数据源表示将sqlSession和DataSourceTransactionManager都使用我们实际使用的数据源进行创建 @Bean public SqlSessionFactory sessionFactory(@Qualifier("dynamicDb") DataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml")); bean.setDataSource(dynamicDataSource); return bean.getObject(); } @Bean public SqlSessionTemplate sqlTemplate(@Qualifier("sessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } @Bean(name = "dataSourceTx") public DataSourceTransactionManager dataSourceTx(@Qualifier("dynamicDb") DataSource dynamicDataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dynamicDataSource); return dataSourceTransactionManager; } }
AbstractRoutingDataSource
```java
public class DynamicDataSource extends AbstractRoutingDataSource { @Override //该方法是获取当前的键值以便拿到对应的数据源,配合我们定义的注解实时修改当前键值,如果我们不需要手动能指定数据源的功能,我们也可以这里直接读操作就将从库的键进行轮询然后返回,如果是写操作就返回主数据源对应的键 protected Object determineCurrentLookupKey() { return DataSourceContextHolder.get(); } }
```java
//ThreadLocal用于存储实时的键
public class DataSourceContextHolder {
private static final ThreadLocal<String> DYNAMIC_DATASOURCE_CONTEXT = new ThreadLocal<>();
public static void set(String datasourceType) {
DYNAMIC_DATASOURCE_CONTEXT.set(datasourceType);
}
public static String get() {
return DYNAMIC_DATASOURCE_CONTEXT.get();
}
public static void clear() {
DYNAMIC_DATASOURCE_CONTEXT.remove();
}
}
@Getter
public enum DynamicDataSourceEnum {
MASTER("master"),
SLAVE("slave");
private String dataSourceName;
DynamicDataSourceEnum(String dataSourceName) {
this.dataSourceName = dataSourceName;
}
}
注解实现手动切换数据源
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSourceSelector {
//指定数据源对应的键,指定是否需要清除数据源
DynamicDataSourceEnum value() default DynamicDataSourceEnum.MASTER;
boolean clear() default true;
}
@Slf4j
@Aspect
@Order(value = 1)
@Component
public class DataSourceContextAop {
@Around("@annotation(com.xjt.proxy.dynamicdatasource.DataSourceSelector)")
public Object setDynamicDataSource(ProceedingJoinPoint pjp) throws Throwable {
boolean clear = true;
try {
Method method = this.getMethod(pjp);
DataSourceSelector dataSourceImport = method.getAnnotation(DataSourceSelector.class);
clear = dataSourceImport.clear();
DataSourceContextHolder.set(dataSourceImport.value().getDataSourceName());
log.info("========数据源切换至:{}", dataSourceImport.value().getDataSourceName());
return pjp.proceed(); //执行被增强的原本方法
} finally {
if (clear) {
DataSourceContextHolder.clear();
}
}
}
//通过切入点获取切入点方法对象,以拿到自定义注解
private Method getMethod(JoinPoint pjp) {
MethodSignature signature = (MethodSignature)pjp.getSignature();
return signature.getMethod();
}
}
测试
@Autowired
private UserMapper userMapper;
@DataSourceSelector(value = DynamicDataSourceEnum.MASTER)
public int update(Long userId) {
User user = new User();
user.setUserId(userId);
user.setUserName("老薛");
return userMapper.updateByPrimaryKeySelective(user);
}