当前位置: 首页 > news >正文

销售员做网站wordpress首页如何调用最新文章评论标签云文章分类等代码

销售员做网站,wordpress首页如何调用最新文章评论标签云文章分类等代码,天津国际工程建设监理公司网站,网站建设及推广开发一、前言 现在的 Web 应用大都是读多写少。除了缓存以外还可以通过数据库 “主从复制” 架构#xff0c;把读请求路由到从数据库节点上#xff0c;实现读写分离#xff0c;从而大大提高应用的吞吐量。 通常#xff0c;我们在 Spring Boot 中只会用到一个数据源#xff0…一、前言 现在的 Web 应用大都是读多写少。除了缓存以外还可以通过数据库 “主从复制” 架构把读请求路由到从数据库节点上实现读写分离从而大大提高应用的吞吐量。 通常我们在 Spring Boot 中只会用到一个数据源即通过 spring.datasource 进行配置。前文 《在 Spring Boot 中配置和使用多个数据源》 介绍了一种在 Spring Boot 中定义、使用多个数据源的方式。但是这种方式对于实现 “读写分离” 的场景不太适合。首先多个数据源都是通过 Bean 定义的当需要新增额外的从数据库时需要改动代码非常不够灵活。其次在业务层中如果需要根据读、写场景切换不同数据源的话只能手动进行。 对于 Spring Boot “读写分离” 架构下的的多数据源我们需要实现如下需求 可以通过配置文件新增数据库从库而不不需要修改代码。自动根据场景切换读、写数据源对业务层是透明的。 幸运的是Spring Jdbc 模块类提供了一个 AbstractRoutingDataSource 抽象类可以实现我们的需求。 它本身也实现了 DataSource 接口表示一个 “可路由” 的数据源。 核心的代码如下 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {// 维护的所有数据源Nullableprivate MapObject, DataSource resolvedDataSources;// 默认的数据源Nullableprivate DataSource resolvedDefaultDataSource;// 获取 Jdbc 连接Overridepublic Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection();}Overridepublic Connection getConnection(String username, String password) throws SQLException {return determineTargetDataSource().getConnection(username, password);}// 获取目标数据源protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, DataSource router not initialized);// 调用 determineCurrentLookupKey() 抽象方法获取 resolvedDataSources 中定义的 key。Object lookupKey determineCurrentLookupKey();DataSource dataSource this.resolvedDataSources.get(lookupKey);if (dataSource null (this.lenientFallback || lookupKey null)) {dataSource this.resolvedDefaultDataSource;}if (dataSource null) {throw new IllegalStateException(Cannot determine target DataSource for lookup key [ lookupKey ]);}return dataSource;}// 抽象方法返回 resolvedDataSources 中定义的 key。需要自己实现Nullableprotected abstract Object determineCurrentLookupKey(); }核心代码如上它的工作原理一目了然。它在内部维护了一个 MapObject, DataSource 属性维护了多个数据源。 当尝试从 AbstractRoutingDataSource 数据源获取数据源连接对象 Connection 时会调用 determineCurrentLookupKey() 方法得到一个 Key然后从数据源 MapObject, DataSource 中获取到真正的目标数据源如果 Key 或者是目标数据源为 null 则使用默认的数据源。 得到目标数据数据源后返回真正的 Jdbc 连接。这一切对于使用到 Jdbc 的组件Repository、JdbcTemplate 等来说都是透明的。 了解了 AbstractRoutingDataSource 后我们来看看如何使用它来实现 “读写分离”。 二、实现思路 首先创建自己的 AbstractRoutingDataSource 实现类。把它的默认数据源 resolvedDefaultDataSource 设置为主库从库则保存到 MapObject, DataSource resolvedDataSources 中。 在 Spring Boot 应用中通常使用 Transactional 注解来开启声明式事务它的默认传播级别为 REQUIRED也就是保证多个事务方法之间的相互调用都是在同一个事务中使用的是同一个 Jdbc 连接。它还有一个 readOnly 属性表示是否是只读事务。 于是我们可以通过 AOP 技术在事务方法执行之前先获取到方法上的 Transactional 注解从而判断是读、还是写业务。并且把 “读写状态” 存储到线程上下文ThreadLocal中 在 AbstractRoutingDataSource 的 determineCurrentLookupKey 方法中我们就可以根据当前线程上下文中的 “读写状态” 判断当前是否是只读业务如果是则返回从库 resolvedDataSources 中的 Key反之则返回 null 表示使用默认数据源也就是主库。 三、初始化数据库 首先在本地创建 4 个不同名称的数据库用于模拟 “MYSQL 主从” 架构。 -- 主库 CREATE DATABASE demo_master CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 从库 CREATE DATABASE demo_slave1 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 从库 CREATE DATABASE demo_slave2 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 从库 CREATE DATABASE demo_slave3 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;如上创建了 4 个数据库。1 个主库3 个从库。它们本质上毫无关系并不是真正意义上的主从架构这里只是为了方便演示。 接着在这 4 个数据库下依次执行如下 SQL 创建一张名为 test 的表。 该表只有 2 个字段1 个是 id 表示主键一个是 name 表示名称。 CREATE TABLE test (id int NOT NULL COMMENT ID,name varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 名称,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci;最后初始化数据。往不同的数据库插入对应的记录。 INSERT INTO demo_master.test (id, name) VALUES (1, master); INSERT INTO demo_slave1.test (id, name) VALUES (1, slave1); INSERT INTO demo_slave2.test (id, name) VALUES (1, slave2); INSERT INTO demo_slave3.test (id, name) VALUES (1, slave3);不同数据库节点下 test 表中的 name 字段不同用于区别不同的数据库节点。 四、创建应用 创建 Spring Boot 应用添加 spring-boot-starter-jdbc 和 mysql-connector-j MYSQL 驱动依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId /dependency dependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactId /dependency五、配置定义 我们需要在 application.yaml 中定义上面创建好的所有主、从数据库。 app:datasource:master: # 唯一主库jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_master?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2b8allowMultiQueriestrueusername: rootpassword: rootslave: # 多个从库slave1:jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave1?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2b8allowMultiQueriestrueusername: rootpassword: rootslave2:jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave2?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2b8allowMultiQueriestrueusername: rootpassword: rootslave3:jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave3?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2b8allowMultiQueriestrueusername: rootpassword: root在 app.datasource.master 下配置了唯一的一个主库也就是写库。然后在 app.datasource.slave 下以 Map 形式配置了多个从库也就是读库每个从库使用自定义的名称作为 Key。 数据源的实现使用的是默认的 HikariDataSource并且数据源的配置是按照 HikariConfig 类定义的。也就是说你可以根据 HikariConfig 的属性在配置中添加额外的设置。 有了配置后还需要定义对应的配置类如下 package cn.springdoc.demo.db;import java.util.Map; import java.util.Objects; import java.util.Properties;import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.ConstructorBinding;ConfigurationProperties(prefix app.datasource) // 配置前缀 public class MasterSlaveDataSourceProperties {// 主库private final Properties master;// 从库private final MapString, Properties slave;ConstructorBinding // 通过构造函数注入配置文件中的值public MasterSlaveDataSourceProperties(Properties master, MapString, Properties slave) {super();Objects.requireNonNull(master);Objects.requireNonNull(slave);this.master master;this.slave slave;}public Properties master() {return master;}public MapString, Properties slave() {return slave;} }还需要在 main 类上使用 EnableConfigurationProperties 注解来加载我们的配置类 package cn.springdoc.demo;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.EnableAspectJAutoProxy;import cn.springdoc.demo.db.MasterSlaveDataSourceProperties;SpringBootApplication EnableAspectJAutoProxy EnableConfigurationProperties(value {MasterSlaveDataSourceProperties.class}) // 指定要加载的配置类 public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);} }这里还使用 EnableAspectJAutoProxy 开启了 AOP 的支持后面会用到。 六、创建 MasterSlaveDataSourceMarker 创建一个 MasterSlaveDataSourceMarker 类用于维护当前业务的 “读写状态”。 package cn.springdoc.demo.db;public class MasterSlaveDataSourceMarker {private static final ThreadLocalBoolean flag new ThreadLocalBoolean();// 返回标记public static Boolean get() {return flag.get();}// 写状态标记为主库public static void master() {flag.set(Boolean.TRUE);}// 读状态标记为从库public static void slave() {flag.set(Boolean.FALSE);}// 清空标记public static void clean() {flag.remove();} }通过 ThreadLocalBoolean 在当前线程中保存当前业务的读写状态。 如果 get() 返回 null 或者 true 则表示非只读需要使用主库。反之则表示只读业务使用从库。 七、创建 MasterSlaveDataSourceAop 创建 MasterSlaveDataSourceAop 切面类在事务方法开始之前执行。 package cn.springdoc.demo.db;import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional;Aspect Component Order(Ordered.HIGHEST_PRECEDENCE) // 在事务开始之前执行 public class MasterSlaveDataSourceAop {static final Logger log LoggerFactory.getLogger(MasterSlaveDataSourceAop.class);Pointcut(value annotation(org.springframework.transaction.annotation.Transactional))public void txMethod () {}Around(txMethod())public Object handle (ProceedingJoinPoint joinPoint) throws Throwable {// 获取当前请求的主从标识try {// 获取事务方法上的注解Transactional transactional ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Transactional.class);if (transactional ! null transactional.readOnly()) {log.info(标记为从库);MasterSlaveDataSourceMarker.slave(); // 只读从库} else {log.info(标记为主库);MasterSlaveDataSourceMarker.master(); // 可写主库}// 执行业务方法Object ret joinPoint.proceed();return ret;} catch (Throwable e) {throw e;} finally {MasterSlaveDataSourceMarker.clean();}} }首先通过 Order(Ordered.HIGHEST_PRECEDENCE) 注解保证它必须比声明式事务 AOP 更先执行。 该 AOP 会拦截所有声明了 Transactional 的方法在执行前从该注解获取 readOnly 属性从而判断是否是只读业务并且在 MasterSlaveDataSourceMarker 标记。 八、创建 MasterSlaveDataSource 现在创建 AbstractRoutingDataSource 的实现类 MasterSlaveDataSource package cn.springdoc.demo.db;import java.util.List; import java.util.concurrent.atomic.AtomicInteger;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class MasterSlaveDataSource extends AbstractRoutingDataSource {static final Logger log LoggerFactory.getLogger(MasterSlaveDataSource.class);// 从库的 Key 列表private ListObject slaveKeys;// 从库 key 列表的索引private AtomicInteger index new AtomicInteger(0);Overrideprotected Object determineCurrentLookupKey() {// 当前线程的主从标识Boolean master MasterSlaveDataSourceMarker.get();if (master null || master || this.slaveKeys.isEmpty()) {// 主库返回 null使用默认数据源log.info(数据库路由主库);return null;}// 从库从 slaveKeys 中选择一个 Keyint index this.index.getAndIncrement() % this.slaveKeys.size();if (this.index.get() 9999999) {this.index.set(0); }Object key slaveKeys.get(index);log.info(数据库路由从库 {}, key);return key;}public ListObject getSlaveKeys() {return slaveKeys;}public void setSlaveKeys(ListObject slaveKeys) {this.slaveKeys slaveKeys;} }其中定义了一个 ListObject slaveKeys 字段用于存储在配置文件中定义的所有从库的 Key。 在 determineCurrentLookupKey 方法中判断当前业务的 “读写状态”如果是只读则通过 AtomicInteger 原子类自增后从 slaveKeys 轮询出一个从库的 Key。反之则返回 null 使用主库。 九、创建 MasterSlaveDataSourceConfiguration 配置类 最后需要在 Configuration 配置类中创建 MasterSlaveDataSource 数据源 Bean。 package cn.springdoc.demo.db;import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties;import javax.sql.DataSource;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource;Configuration public class MasterSlaveDataSourceConfiguration {Beanpublic DataSource dataSource(MasterSlaveDataSourceProperties properties) {MasterSlaveDataSource dataSource new MasterSlaveDataSource();// 主数据库dataSource.setDefaultTargetDataSource(new HikariDataSource(new HikariConfig(properties.master())));// 从数据库MapObject, Object slaveDataSource new HashMap();// 从数据库 KeydataSource.setSlaveKeys(new ArrayList());for (Map.EntryString,Properties entry : properties.slave().entrySet()) {if (slaveDataSource.containsKey(entry.getKey())) {throw new IllegalArgumentException(存在同名的从数据库定义 entry.getKey());}slaveDataSource.put(entry.getKey(), new HikariDataSource(new HikariConfig(entry.getValue())));dataSource.getSlaveKeys().add(entry.getKey());}// 设置从库dataSource.setTargetDataSources(slaveDataSource);return dataSource;} }首先通过配置方法注入配置类该类定义了配置文件中的主库、从库属性。 使用 HikariDataSource 实例化唯一主库数据源、和多个从库数据源并且设置到 MasterSlaveDataSource 对应的属性中。 同时还存储每个从库的 Key且该 Key 不允许重复。 十、测试 1、创建 TestService 创建用于测试的业务类。 package cn.springdoc.demo.service;import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;Service public class TestService {final JdbcTemplate jdbcTemplate;public TestService(JdbcTemplate jdbcTemplate) {super();this.jdbcTemplate jdbcTemplate;}// 只读Transactional(readOnly true)public String read () {return this.jdbcTemplate.queryForObject(SELECT name FROM test WHERE id 1;, String.class);} // 先读再写Transactionalpublic String write () {this.jdbcTemplate.update(UPDATE test SET name ? WHERE id 1;, new name);return this.read();} }通过构造函数注入 JdbcTemplatespring jdbc 模块自动配置的。 Service 类中定义了 2 个方法。 read()只读业务从表中检索 name 字段返回。write可写业务先修改表中的 name 字段值为 new name然后再调用 read() 方法读取修改后的结果、返回。 2、创建测试类 创建测试类如下 package cn.springdoc.demo.test;import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;import cn.springdoc.demo.service.TestService;SpringBootTest(webEnvironment WebEnvironment.RANDOM_PORT) public class DemoApplicationTests {static final Logger log LoggerFactory.getLogger(DemoApplicationTests.class);AutowiredTestService testService;Testpublic void test() throws Exception {// 连续4次读log.info(read{}, this.testService.read());log.info(read{}, this.testService.read());log.info(read{}, this.testService.read());log.info(read{}, this.testService.read());// 写log.info(write{}, this.testService.write());} }在测试类方法中连续调用 4 次 TestService 的 read() 方法。由于这是一个只读方法按照我们的设定它会在 3 个从库之间轮询使用。由于我们故意把三个从库 test 表中 name 的字段值设置得不一样所以这里可以通过返回的结果看出来是否符合我们的预期。 最后调用了一次 write() 方法按照设定会路由到主库。先 UPDATE 修改数据再调用 read() 读取数据虽然 read() 设置了 Transactional(readOnly true)但因为入口方法是 write()所以 read() 还是会从主库读取数据默认的事务传播级别。 执行测试输出的日志如下 [ main] c.s.demo.db.MasterSlaveDataSourceAop : 标记为从库 [ main] c.s.demo.db.MasterSlaveDataSource : 数据库路由从库 slave1 [ main] c.s.demo.test.DemoApplicationTests : readslave1 [ main] c.s.demo.db.MasterSlaveDataSourceAop : 标记为从库 [ main] c.s.demo.db.MasterSlaveDataSource : 数据库路由从库 slave2 [ main] c.s.demo.test.DemoApplicationTests : readslave2 [ main] c.s.demo.db.MasterSlaveDataSourceAop : 标记为从库 [ main] c.s.demo.db.MasterSlaveDataSource : 数据库路由从库 slave3 [ main] c.s.demo.test.DemoApplicationTests : readslave3 [ main] c.s.demo.db.MasterSlaveDataSourceAop : 标记为从库 [ main] c.s.demo.db.MasterSlaveDataSource : 数据库路由从库 slave1 [ main] c.s.demo.test.DemoApplicationTests : readslave1 [ main] c.s.demo.db.MasterSlaveDataSourceAop : 标记为主库 [ main] c.s.demo.db.MasterSlaveDataSource : 数据库路由主库 [ main] c.s.demo.test.DemoApplicationTests : writenew name你可以看到对于只读业务。确实轮询了三个不同的从库符合预期。最后的 write() 方法也成功地路由到了主库执行了修改并且返回了修改后的结果。 十一总结 通过 AbstractRoutingDataSource 可以不使用任何第三方中间件就可以在 Spring Boot 中实现数据源 “读写分离”这种方式需要在每个业务方法上通过 Transactional 注解明确定义是读还是写。
http://www.sadfv.cn/news/51970/

相关文章:

  • 河南省建设监理协会网站人才十wordpress storage
  • erp管理系统多少钱百度seo标题优化软件
  • 大朗做网站的物流运输做网站的素材
  • 网站建设公司营销方案做360手机网站优化排
  • 珠海主题网站设计模板交流平台网站架构怎么做
  • 商城网站建设需求分析wordpress显示时间代码
  • 星座 网站 建设广西壮族自治区有几个市
  • 建设网站要买空间吗商标注册证在哪里可以查到
  • 用dw做的网站怎么发布wordpress主题nova
  • 坂田网站建设公司互联网去哪里学
  • 凡客诚品网站建设策划书原创文章网站开发教程
  • 中国企业网站建设案例wordpress数据库发布文章
  • 网站网页设计代码软文100字左右案例
  • 芜湖网站建设工作室广西网络干部学院官网
  • 如何防止网站攻击未来产品设计
  • 百度收录网站多久商城网站建设服务哪家好
  • 前端和做网站网站外链建设平台
  • 万网可以花钱做网站昆明网站推广优化公司
  • 简约网站版式国内十大平面设计公司
  • 广州哪家做网站价格好广东短视频推广公司
  • 内蒙古知名网站建设广告模板图片
  • 东莞制作网站公司哪家好域名网址注册
  • 南宁网站定制公司下载百度app下载
  • 360度网站模板上海网站建设的
  • 站长素材网yandex搜索引擎入口
  • 360网站导航公司地址怎么做由音乐学院做的网站
  • app设计模板网站域名是什么意思
  • 找人开发一个网站多少钱建筑工程承包合同书
  • 企业网站的首页展示网站多少钱一个
  • 帮别人设计网站的网站吗成都企业网站排名优化