Spring系列第52篇:Spring实现数据库读写分离,美团面试编程题

public @interface EnableReadWrite {

}

4、案例


读写分离的关键代码写完了,下面我们来上案例验证一下效果。

4.1、执行 sql 脚本

下面准备 2 个数据库:javacode2018_master(主库)、javacode2018_slave(从库)

2 个库中都创建一个 t_user 表,分别插入了一条数据,稍后用这个数据来验证走的是主库还是从库。

DROP DATABASE IF EXISTS javacode2018_master;

CREATE DATABASE IF NOT EXISTS javacode2018_master;

USE javacode2018_master;

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

id   INT PRIMARY KEY       AUTO_INCREMENT,

name VARCHAR(256) NOT NULL DEFAULT ‘’

COMMENT ‘姓名’

);

INSERT INTO t_user (name) VALUE (‘master库’);

DROP DATABASE IF EXISTS javacode2018_slave;

CREATE DATABASE IF NOT EXISTS javacode2018_slave;

USE javacode2018_slave;

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

id   INT PRIMARY KEY       AUTO_INCREMENT,

name VARCHAR(256) NOT NULL DEFAULT ‘’

COMMENT ‘姓名’

);

INSERT INTO t_user (name) VALUE (‘slave库’);

4.2、spring 配置类

@1:启用读写分离

masterDs()方法:定义主库数据源

slaveDs()方法:定义从库数据源

dataSource():定义读写分离路由数据源

后面还有 2 个方法用来定义 JdbcTemplate 和事务管理器,方法中都通过@Qualifier(“dataSource”)限定了注入的 bean 名称为 dataSource:即注入了上面 dataSource()返回的读写分离路由数据源。

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;

import com.javacode2018.readwritesplit.base.EnableReadWrite;

import com.javacode2018.readwritesplit.base.ReadWriteDataSource;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

import java.util.HashMap;

import java.util.Map;

@EnableReadWrite //@1

@Configuration

@ComponentScan

public class MainConfig {

//主库数据源

@Bean

public DataSource masterDs() {

org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();

dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);

dataSource.setUrl(“jdbc:mysql://localhost:3306/javacode2018_master?characterEncoding=UTF-8”);

dataSource.setUsername(“root”);

dataSource.setPassword(“root123”);

dataSource.setInitialSize(5);

return dataSource;

}

//从库数据源

@Bean

public DataSource slaveDs() {

org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();

dataSource.setDriverClassName(“com.mysql.jdbc.Driver”);

dataSource.setUrl(“jdbc:mysql://localhost:3306/javacode2018_slave?characterEncoding=UTF-8”);

dataSource.setUsername(“root”);

dataSource.setPassword(“root123”);

dataSource.setInitialSize(5);

return dataSource;

}

//读写分离路由数据源

@Bean

public ReadWriteDataSource dataSource() {

ReadWriteDataSource dataSource = new ReadWriteDataSource();

//设置主库为默认的库,当路由的时候没有在datasource那个map中找到对应的数据源的时候,会使用这个默认的数据源

dataSource.setDefaultTargetDataSource(this.masterDs());

//设置多个目标库

Map<Object, Object> targetDataSources = new HashMap<>();

targetDataSources.put(DsType.MASTER, this.masterDs());

targetDataSources.put(DsType.SLAVE, this.slaveDs());

dataSource.setTargetDataSources(targetDataSources);

return dataSource;

}

//JdbcTemplate,dataSource为上面定义的注入读写分离的数据源

@Bean

public JdbcTemplate jdbcTemplate(@Qualifier(“dataSource”) DataSource dataSource) {

return new JdbcTemplate(dataSource);

}

//定义事务管理器,dataSource为上面定义的注入读写分离的数据源

@Bean

public PlatformTransactionManager transactionManager(@Qualifier(“dataSource”) DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

}

4.3、UserService

这个类就相当于我们平时写的 service,我是为了方法,直接在里面使用了 JdbcTemplate 来操作数据库,真实的项目操作 db 会放在 dao 里面。

getUserNameById 方法:通过 id 查询 name。

insert 方法:插入数据,这个内部的所有操作都会走主库,为了验证是不是查询也会走主库,插入数据之后,我们会调用 this.userService.getUserNameById(id, DsType.SLAVE)方法去执行查询操作,第二个参数故意使用 SLAVE,如果查询有结果,说明走的是主库,否则走的是从库,这里为什么需要通过 this.userService 来调用 getUserNameById?

this.userService 最终是个代理对象,通过代理对象访问其内部的方法,才会被读写分离的拦截器拦截。

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;

import com.javacode2018.readwritesplit.base.IService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.stereotype.Component;

import org.springframework.transaction.annotation.Propagation;

import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Component

public class UserService implements IService {

@Autowired

private JdbcTemplate jdbcTemplate;

@Autowired

private UserService userService;

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

public String getUserNameById(long id, DsType dsType) {

String sql = “select name from t_user where id=?”;

List list = this.jdbcTemplate.queryForList(sql, String.class, id);

return (list != null && list.size() > 0) ? list.get(0) : null;

}

//这个insert方法会走主库,内部的所有操作都会走主库

@Transactional

public void insert(long id, String name) {

System.out.println(String.format(“插入数据{id:%s, name:%s}”, id, name));

this.jdbcTemplate.update(“insert into t_user (id,name) values (?,?)”, id, name);

String userName = this.userService.getUserNameById(id, DsType.SLAVE);

System.out.println(“查询结果:” + userName);

}

}

4.4、测试用例

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;

import org.junit.Before;

import org.junit.Test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo1Test {

UserService userService;

@Before

public void before() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

context.register(MainConfig.class);

context.refresh();

this.userService = context.getBean(UserService.class);

}

@Test

public void test1() {

System.out.println(this.userService.getUserNameById(1, DsType.MASTER));

System.out.println(this.userService.getUserNameById(1, DsType.SLAVE));

}

@Test

public void test2() {

long id = System.currentTimeMillis();

System.out.println(id);

this.userService.insert(id, “张三”);

}

}

test1 方法执行 2 次查询,分别查询主库和从库,输出:

master库

slave库

是不是很爽,由开发者自己控制具体走主库还是从库。

test2 执行结果如下,可以看出查询到了刚刚插入的数据,说明 insert 中所有操作都走的是主库。

1604905117467

插入数据{id:1604905117467, name:张三}

查询结果:张三

5、案例源码


git地址:

https://gitee.com/javacode2018/spring-series

本文案例对应源码:

spring-series\lesson-004-readwritesplit

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**

image

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-chXAg4M9-1712768111332)]

最后

现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**

[外链图片转存中…(img-A47OBpy5-1712768111332)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-N4U4GhD3-1712768111333)]

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值