数据源

类型

  • javax.sql.DataSource

  • javax.sql.XADataSource

  • org.springframework.jdbc.datasource.embedded,EnbeddedDataSource

Spring Boot 中的数据源

单数据源(官方推荐微服务使用单数据源)

  • 数据库连接池

    • Apache Commons DBCP

    • Tomcat DBCP

多数据源

实际生产中很可能会出现.

事务

  • Spring 中的事务通过PlatformTransactionManagere 管理, 使用 AOP 实现, 具体实现类是 TransactionInterceptor.

  • 事务传播(propagation)与保护点(savepoint): 二者密切相关.

Spring Boot 使用JDBC

单数据源

  1. 配置:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/product?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=xlx
spring.datasource.password=xlx

注意serverTimezone的配置, 亚洲一般配置为 Hongkong.

  1. 访问类注入DataSource
@Repository
public class ProductRepository { @Autowired
private DataSource dataSource;
...
}

多数据源配置

  1. 配置
spring.ds1.name=master
spring.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.ds1.url=jdbc:mysql://localhost:3306/product?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.ds1.username=xlx
spring.ds1.password=xlx spring.ds2.name=slave
spring.ds2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.ds2.url=jdbc:mysql://localhost:3306/product?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.ds2.username=xlx
spring.ds2.password=xlx
  1. 继承 AbstractRoutingDataSource 抽象类并实现方法determineCurrentLookupKey()

这个类就是需要使用的数据源类型.

public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbTypeContextHolder.get();
}
}

查看 AbstractRoutingDataSource 的源码可以发现, 其定义了一个 Map<Object, Object> targetDataSources; 用来保存多数据源, 而上面重写的方法返回的就是需要使用的数据源的key.

public enum DBType {
MASTER,
SLAVE
}
  1. 定义 DbTypeContextHolder 其中使用一个 ThreadLocal 来保存key, 三个方法都必不可少. 分别用来获取, 设置, 清除.
@Slf4j
public class DbTypeContextHolder { private final static ThreadLocal<DBType> holder = new ThreadLocal<DBType>(); private final static ThreadLocal<Boolean> alwaysWrite = new ThreadLocal<Boolean>(); public static DBType get() {
if (alwaysWrite.get() != null && alwaysWrite.get()) {
log.info("将强制使用Write");
return DBType.MASTER;
}
log.info("获取到: "+holder.get().name());
return holder.get();
} public static void set(DBType dbType) {
log.info("设置为: "+dbType.name());
holder.set(dbType);
} public static void clean() {
log.info("清除...");
holder.remove();
} public static void setAlwaysWrite() {
log.info("设置强制Write");
alwaysWrite.set(true);
} public static void cleanAlwaysWrite() {
log.info("清除强制Write");
clean();
alwaysWrite.remove();
} }
  1. 定义数据源配置类 MultiDataSourceConfiguration
@Configuration
public class MultiDataSourceConfiguration { @Autowired
Environment environment; @Bean
public DataSource getDynamicDataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object,Object> map = new HashMap<>(); for (int i = 1; i < 3 ; i++) {
String name = environment.getProperty("spring.ds"+String.valueOf(i)+".name");
if (name==null) break;
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(environment.getProperty("spring.ds"+String.valueOf(i)+".driver-class-name"))
.username(environment.getProperty("spring.ds"+String.valueOf(i)+".username"))
.url(environment.getProperty("spring.ds"+String.valueOf(i)+".url"))
.password(environment.getProperty("spring.ds"+String.valueOf(i)+".password"))
.build();
if (name.trim().toUpperCase().equals("MASTER")){
dynamicDataSource.setDefaultTargetDataSource(dataSource);
map.put(DBType.MASTER,dataSource);
}else{
map.put(DBType.SLAVE,dataSource);
}
} dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
}
  1. 检查是否可用
public Boolean save(Product product) throws SQLException {
System.out.println("save product : "+product);
DbTypeContextHolder.set(DBType.MASTER);
System.out.println(dataSource.getConnection());
DbTypeContextHolder.clean(); DbTypeContextHolder.set(DBType.SLAVE);
System.out.println(dataSource.getConnection());
DbTypeContextHolder.clean();
return true;
}

结果如下,说明是两个不同的数据源:

HikariProxyConnection@1667860231 wrapping com.mysql.cj.jdbc.ConnectionImpl@78794b01
HikariProxyConnection@556437990 wrapping com.mysql.cj.jdbc.ConnectionImpl@314c0916
  1. 自动切换

可以定义切面来自动切换数据源. 比如普通的读写分离.

读写分离是通过定义在仓储层方法上的规则来实现, 同时也定义了在服务层必须使用写库来读取数据的方式.

除此之外, 如果使用 @Transactional , 因为其也是通过AOP方式实现, 所以与自动切换的AOP存在顺序关系, 为了能在 @Transactional 事务前设置好数据源, 需要增加 @Order(0) .

@Aspect
@Component
@Order(0)
public class DataSourceAspect { // 必须使用写库的
@Pointcut("execution(* com.xlx.product.repository.repo..*.save*(..)) || execution(* com.xlx.product.repository.repo..*.insert*(..)) || execution(* com.xlx.product.repository.repo..*.update*(..))")
public void write(){} @Before("write()")
public void beforeWrite(JoinPoint joinPoint){
System.out.println("beforeWrite()"+joinPoint.getSignature().getName());
DbTypeContextHolder.set(DBType.MASTER);
} @After("write()")
public void afterWrite(JoinPoint joinPoint){
DbTypeContextHolder.clean();
} // 必须使用读库的
@Pointcut("execution(* com.xlx.product.repository.repo..*.get*(..))|| execution(* com.xlx.product.repository.repo..*.select*(..))||execution(* com.xlx.product.repository.repo..*.count*(..))")
public void read(){} @Before("read()")
public void beforeRead(JoinPoint joinPoint){
DbTypeContextHolder.set(DBType.SLAVE);
} @After("read()")
public void afterRead(JoinPoint joinPoint){
DbTypeContextHolder.clean();
} // 服务层有注解的方法特殊处理
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)" +
"||@annotation(com.xlx.product.repository.annotation.DBTypeAnnotation)")
public void readWrite(){} @Before("readWrite()")
public void before(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
if(methodSignature.getMethod().isAnnotationPresent(Transactional.class)) DbTypeContextHolder.set(DBType.MASTER);
if(methodSignature.getMethod().isAnnotationPresent(DBTypeAnnotation.class)) DbTypeContextHolder.setAlwaysWrite();
} @After("readWrite()")
public void after(JoinPoint joinPoint){
DbTypeContextHolder.clean();
DbTypeContextHolder.cleanAlwaysWrite();
} }
/**
* 放置在方法上的注解, 用来指定使用的数据源类型, 默认使用主数据源
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DBTypeAnnotation { @AliasFor("values")
DBType dbType() default DBType.MASTER; @AliasFor("dbType")
DBType values() default DBType.MASTER;
}
  1. service 和 controller
@Service
@Slf4j
public class ProductService { @Autowired
ProductRepository repository; public String findAndSave(){
log.info("findAndSave() 方法开始.....");
String rs = testFunc();
log.info("findAndSave() 方法结束.....");
return rs;
} @DBTypeAnnotation
public String findAndSaveWithWrite(){
log.info("findAndSaveWithWrite() 方法开始.....");
String rs = testFunc();
log.info("findAndSaveWithWrite() 方法结束.....");
return rs;
} private String testFunc() {
List<Product> productList = repository.selectAll();
repository.saveProduct(productList.get(0));
productList = repository.selectAll();
repository.saveProduct(productList.get(0));
return "ok";
} }
@RestController
public class ProductController { @Autowired
ProductService productService; @GetMapping("/product/findAndSave")
public String findAndSave()throws SQLException {
return productService.findAndSave();
} @GetMapping("/product/findAndSaveWithWrite")
public String findAndSaveWithWrite()throws SQLException {
return productService.findAndSaveWithWrite();
}
}

Spring Boot系列(三) Spring Boot 之 JDBC的更多相关文章

  1. Spring框架系列(2) - Spring简单例子引入Spring要点

    上文中我们简单介绍了Spring和Spring Framework的组件,那么这些Spring Framework组件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework组件 ...

  2. Spring框架系列(6) - Spring IOC实现原理详解之IOC体系结构设计

    在对IoC有了初步的认知后,我们开始对IOC的实现原理进行深入理解.本文将帮助你站在设计者的角度去看IOC最顶层的结构设计.@pdai Spring框架系列(6) - Spring IOC实现原理详解 ...

  3. Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等)

    上文,我们看了IOC设计要点和设计结构:以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的:容器中存放的是Bean的定义即Be ...

  4. Spring框架系列(12) - Spring AOP实现原理详解之JDK代理实现

    上文我们学习了SpringAOP Cglib动态代理的实现,本文主要是SpringAOP JDK动态代理的案例和实现部分.@pdai Spring框架系列(12) - Spring AOP实现原理详解 ...

  5. Spring框架系列(7) - Spring IOC实现原理详解之IOC初始化流程

    上文,我们看了IOC设计要点和设计结构:紧接着这篇,我们可以看下源码的实现了:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的. ...

  6. Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现

    前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的.本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所 ...

  7. Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建

    上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...

  8. Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现

    我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理.@pdai Spring框架系列 ...

  9. 【Spring Boot&&Spring Cloud系列】Spring Boot中使用NoSql数据库Redis

    github地址:https://github.com/AndyFlower/Spring-Boot-Learn/tree/master/spring-boot-nosql-redis 一.加入依赖到 ...

随机推荐

  1. 一、.net core 集成vue

    一.npm安装WebPack (这种情况就会出现在项目外部,只不过借用VS的程序包管理器控制台获取而已) 右击新建项目创建webpack

  2. poj3494Largest Submatrix of All 1’s(最大全1子矩阵)

    题目链接:http://poj.org/problem?id=3494 题目大意: 出1个M*N的矩阵M1,里面的元素只有0或1,找出M1的一个子矩阵M2,M2中的元素只有1,并且M2的面积是最大的. ...

  3. 深度强化学习(Deep Reinforcement Learning)入门:RL base & DQN-DDPG-A3C introduction

    转自https://zhuanlan.zhihu.com/p/25239682 过去的一段时间在深度强化学习领域投入了不少精力,工作中也在应用DRL解决业务问题.子曰:温故而知新,在进一步深入研究和应 ...

  4. Tensorflow揭秘

    https://www.bilibili.com/video/av64970827/?p=7 tf2.0主要使用tf.keras api来构建模型,主要包括如下几个部分 一.Layers 如下是一些特 ...

  5. CSP-S 赛前模板复习

    快读模板 这个连算法都算不上... inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9') ...

  6. mac下安装 rabbitMq

    1.安装HomeBrew,如果已经安装这一步跳过. 2.用brew install rabbitmq指令即可进行rabbitmq服务的自动安装. 3.安装完成之后会出现一下提示:   rabbit安装 ...

  7. 题解 P3166 【[CQOI2014]数三角形】

    做完之后看了看题解,怎么一篇和我思路一样的也没有...我好慌啊qwq(所以一定是窝太弱了看不懂dalao的思路) 好吧窝的方法确实很奇怪: 核心代码只有3行 输入 循环 输出 一气呵成 是题解中的豪杰 ...

  8. mongdb 学习

    一:安装1.首先到官网(http://www.mongodb.org/downloads )下载合适的安装包2.安装mongodb3. cmd 命令切换到安装目录bin 下面 mongod --dbp ...

  9. sharepoint 2010 创建自定义的ASP.NET Web Service (上)

    项目背景 根据客户需求在SharePoint 2010 中创建自定义的ASP.NET Web Service可以分为3种方式(我所知道的).废话少说,下面一一列举: 创建方式 MSDN 官方博客自己的 ...

  10. 处理后台传过来的json数据-显示到微信小程序的富文本里

    解析数据: JSON.parse(); 获取 加密的文章内容, 将解密文章内容, 将解密后的img标签的路径换成绝对地址(服务器) 调整图片的大小,