在现代软件开发中,数据库设计和查询优化是至关重要的。阿里巴巴 Java 开发手册中明确指出,使用多张表进行 JOIN
操作时不应超过三张表,因为超出这一数量会带来显著的性能问题。尤其是在数据量大、查询复杂的情况下,这种多表连接会导致查询效率低下,增加数据库负担,甚至可能影响系统的响应时间。
面对这些挑战,开发者们常常需要寻找有效的解决方案以优化查询性能。传统的做法是尽量减少 JOIN
操作的复杂度,但这并不总是可行。尤其是在需要从多个数据源获取信息时,如何高效地整合这些数据成为一个难题。
在本文中,我们提出了一种改进的方法——通过引入中间表来简化多表 JOIN
操作。具体来说,我们将多个表的数据冗余到一个中间表中,从而减少了查询时的 JOIN
复杂度。此外,我们还结合了观察者模式来解决数据同步的痛点问题,从而确保数据一致性,并提高了系统的维护性和性能。
接下来,我们将详细探讨这一方法的实现过程和效果,包括中间表的设计、观察者模式的应用,以及实际案例中的表现。
多表 JOIN 产生的问题 在关系型数据库中,JOIN
操作用于将来自不同表的数据合并在一起。虽然这种操作在处理多表数据时非常有用,但当涉及到多张表的 JOIN
时,会面临一系列挑战和问题。
随着 JOIN
的表数增加,数据库在执行查询时需要处理的计算量也随之增加。这通常导致以下性能问题:
查询延迟 :多表 JOIN
可能导致查询时间显著增加,尤其是在数据量较大的情况下。数据库需要对多个表的数据进行排序、合并和计算,这些操作都消耗大量的计算资源
索引效率下降 :虽然在单表 JOIN
中合理使用索引可以提高查询效率,但在多表 JOIN
中,索引的效果可能会被削弱,特别是当连接条件复杂或表的数据量很大时
随着 JOIN
的表数增加,数据库在执行查询时需要处理的计算量也随之增加。这通常导致以下性能问题:
查询语句复杂 :多表 JOIN
的查询语句通常非常复杂,涉及到多个表的条件、连接类型以及字段选择。这样的复杂查询不仅难以编写和维护,还容易出错
调试困难 :当查询出现性能问题或错误时,调试复杂的多表 JOIN
语句往往很困难。定位和修复问题可能需要大量的时间和精力
横向扩展困难 :当系统需要扩展以处理更多数据时,多表 JOIN
的复杂性会进一步增加,尤其是在分布式数据库或分片环境中。有效地管理和优化这些查询变得更加困难
在应对这些挑战时,开发者需要寻找优化策略和方法,以提高查询性能和系统的可维护性。接下来,我们将介绍一种有效的解决方案——通过引入中间表来简化查询,并结合观察者模式来优化数据同步问题。 解决方案:使用中间表 中间表的概念 在处理多表 JOIN
操作时,中间表是一种有效的优化策略。中间表,顾名思义,是一种用于简化查询和数据管理的表,它存储了从多个源表中冗余的数据。通过将多个表的相关字段预先合并到一个中间表中,可以减少查询时需要进行的 JOIN
操作,从而降低查询的复杂性和提高性能。
中间表的主要优势包括 :
简化查询 :减少复杂的多表 JOIN
操作,只需查询中间表即可获得所需的数据
提高性能 :通过减少实时计算和连接操作,提高查询的执行效率
设计中间表 假设我们现在有以下表: goods
(商品表)、inventory
(库存表)、purchase
(采购表)。他们的表字段分别为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 MariaDB [blog]> desc goods; +---------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------+--------------+------+-----+---------+-------+ | id | char(19) | NO | PRI | NULL | | | name | varchar(64) | NO | | NULL | | | description | varchar(255) | YES | | NULL | | | status | tinyint(4) | NO | | NULL | | | specification | varchar(255) | YES | | NULL | | +---------------+--------------+------+-----+---------+-------+ 5 rows in set (0.001 sec) # 字段依次为 id 、商品名称、商品简介、商品状态、商品规格 MariaDB [blog]> desc inventory; +----------+----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------+----------+------+-----+---------+-------+ | id | char(19) | NO | PRI | NULL | | | goods_id | char(19) | NO | | NULL | | | quantity | int(11) | NO | | 0 | | +----------+----------+------+-----+---------+-------+ 3 rows in set (0.001 sec) # 字段依次为 id 、商品id 、库存 MariaDB [blog]> desc purchase; +---------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------+--------------+------+-----+---------+-------+ | id | char(19) | NO | PRI | NULL | | | goods_id | char(19) | NO | | NULL | | | purchase_date | date | NO | | NULL | | | quantity | int(11) | NO | | 0 | | | quantity | int(11) | NO | | 0 | | | supplier_name | varchar(128) | NO | | NULL | | +---------------+--------------+------+-----+---------+-------+ 5 rows in set (0.001 sec) # 字段依次为 id 、商品id 、采购日期、采购数量、供应商名称 MariaDB [blog]>
假设业务需求是,在填写采购单时,页面需要自动带出选中商品的最新库存信息(不考虑业务合理性哈,这里只是示例)。为此,我们可以通过创建一个中间表 inventory_goods
,将 goods
和 inventory
表中的字段冗余到这个中间表中,从而简化查询。
原始的查询语句应该为: 1 2 3 4 5 6 7 8 9 10 11 SELECT a.id AS id, g.id AS goods_id, g.NAME AS goods_name, i.quantity AS latest_quantity FROM purchase a LEFT JOIN goods g ON a.goods_id = g.id LEFT JOIN inventory i ON g.id = i.goods_id WHERE a.id = '5555555555555555555' ;
现在我们将 goods
和 inventory
的字段冗余到一个中间表中 inventory_goods
: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 MariaDB [blog]> desc inventory_goods; +---------------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------------+--------------+------+-----+---------+-------+ | id | char(19) | NO | PRI | NULL | | | inventory_id | char(19) | NO | | NULL | | | goods_id | char(19) | NO | | NULL | | | quantity | int(11) | NO | | 0 | | | goods_name | varchar(64) | NO | | NULL | | | goods_description | varchar(255) | YES | | NULL | | | goods_status | tinyint(4) | NO | | NULL | | | goods_specification | varchar(255) | YES | | NULL | | +---------------------+--------------+------+-----+---------+-------+ 8 rows in set (0.001 sec)
使用中间表 inventory_goods
后,我们可以将查询语句简化为: 1 2 3 4 5 6 7 8 9 10 SELECT a.id AS id, ig.id AS goods_id, ig.NAME AS goods_name, ig.quantity AS latest_quantity FROM purchase a LEFT JOIN inventory_goods ig ON a.goods_id = ig.goods_id WHERE a.id = '5555555555555555555' ;
新的查询语句只需访问中间表 inventory_goods
,避免了复杂的多表 JOIN
操作。这不仅简化了查询逻辑,还提高了查询性能。
这种方法显著简化了查询逻辑,并提高了查询效率。通过使用中间表,我们能够避免多表 JOIN
操作所带来的性能开销,使查询更加高效且易于维护。然而,在实际业务中,涉及的表可能不止两个,可能有几个甚至十几个表。在这种情况下,中间表的数据同步更新就成为一个重要的问题。接下来,我们将详细探讨如何解决这个问题。 观察者模式的应用 观察者模式简介 观察者模式是一种行为设计模式,用于建立一种对象之间的一对多依赖关系。当一个对象(称为“主题”)的状态发生变化时,它的所有依赖对象(称为“观察者”)都会被自动通知并更新。这种模式通常用于实现事件驱动的系统和解耦的设计。
基本概念包括:
主题 :状态发生变化的对象。它维护一个观察者列表,负责注册和通知观察者
观察者 :对主题的状态变化做出响应的对象。当主题状态发生变化时,观察者会接收到通知并执行相应的操作
通知机制 :当主题的状态变化时,通知所有注册的观察者,以便它们可以进行相应的更新操作
解决数据同步问题 在我们的案例中,数据同步问题指的是如何在表 inventory
或表 goods
的数据发生变化时,将这些变化同步到中间表 inventory_goods
。为了解决这个问题,我们可以使用观察者模式来实现数据的自动同步。
设计主题和观察者 主题 :表 inventory
和表 goods
,它们是数据变化的源
观察者 :中间表 inventory_goods
,它需要同步更新以反映表 inventory
和表 goods
的最新数据
实现思路 定义事件类 :定义一个自定义事件类,可以包含 inventory
和表 goods
表的相关数据
在服务类实现触发事件方法 :在 inventory
和表 goods
表的服务类中,触发事件以便通知监听器
实现事件监听器 :创建一个事件监听器,处理 ObserverEvent
并同步更新 inventory_goods
表
具体实现 自定义事件类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Getter public class ObserverEvent extends ApplicationEvent { private final Object data; private final String tableName; public ObserverEvent (Object source, Object data, String tableName) { super (source); this .data = data; this .tableName = tableName; } }
GoodsServiceImpl 服务实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Service @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class GoodsServiceImpl extends ServiceImpl <GoodsMapper, Goods> implements IGoodsService { private final ApplicationEventPublisher eventPublisher; @Override public void updateGoods (Goods goods) { this .updateById(goods); eventPublisher.publishEvent(new ObserverEvent (this , goods, Goods.class.getSimpleName())); } }
InventoryServiceImpl 服务实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Service @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class InventoryServiceImpl extends ServiceImpl <InventoryMapper, Inventory> implements IInventoryService { private final ApplicationEventPublisher eventPublisher; @Override public void updateInventory (Inventory inventory) { this .updateById(inventory); eventPublisher.publishEvent(new ObserverEvent (this , inventory, Inventory.class.getSimpleName())); } }
IInventoryGoodsService 服务接口、服务实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface IInventoryGoodsService extends IService <InventoryGoods> { @Async void syncUpdateByGoods (Object goods) ; @Async void syncUpdateByInventory (Object inventory) ; }
这里使用了 @Async
注解,需在配置类添加 @EnableAsync
启动异步支持,且建议自行实现线程池而非使用默认的 SimpleAsyncTaskExecutor
(当然如不需要也可以去掉 @Async
) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Service public class InventoryGoodsServiceImpl extends ServiceImpl <InventoryGoodsMapper, InventoryGoods> implements IInventoryGoodsService { @Override public void syncUpdateByGoods (Object goods) { if (goods instanceof List && ObjectUtils.isNotEmpty (goods) && ((List<?>) goods).get(0 ) instanceof Goods) { updateGoodsBatch((List<Goods>) goods); } else if (goods instanceof Goods) { updateGoods((Goods) goods); } } @Override public void syncUpdateByInventory (Object inventory) { if (inventory instanceof List && ObjectUtils.isNotEmpty (inventory) && ((List<?>) inventory).get(0 ) instanceof Inventory) { updateInventoryBatch((List<Inventory>) inventory); } else if (inventory instanceof Inventory) { updateInventory((Inventory) inventory); } } private void updateGoodsBatch (List<Goods> goodsList) { goodsList.forEach(this ::updateGoods); } private void updateInventoryBatch (List<Inventory> inventoryList) { inventoryList.forEach(this ::updateInventory); } private void updateGoods (Goods data) { lambdaUpdate().eq(InventoryGoods::getGoodsId, data.getId()) .set(InventoryGoods::getGoodsName, data.getName()) .set(InventoryGoods::getGoodsStatus, data.getStatus()) .set(InventoryGoods::getGoodsDescription, data.getDescription()) .set(InventoryGoods::getGoodsSpecification, data.getSpecification()) .update(); } private void updateInventory (Inventory data) { lambdaUpdate() .eq(InventoryGoods::getInventoryId, data.getId()) .set(InventoryGoods::getQuantity, data.getQuantity()) .update(); } }
实现事件监听器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Component @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class ObserverListener implements ApplicationListener <ObserverEvent> { private final IInventoryGoodsService iInventoryGoodsService; private final Map<String, Consumer<Object>> handlers = new HashMap <>(); @PostConstruct public void init () { handlers.put(Inventory.class.getSimpleName(), iInventoryGoodsService::syncUpdateByInventory); handlers.put(Goods.class.getSimpleName(), iInventoryGoodsService::syncUpdateByGoods); } @Override public void onApplicationEvent (ObserverEvent event) { String tableName = event.getTableName(); Object data = event.getData(); Consumer<Object> handler = handlers.get(tableName); if (ObjectUtils.isEmpty(handler)) { return ; } handler.accept(data); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Component @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class ObserverListener implements ApplicationListener <ObserverEvent> { private final IInventoryGoodsService iInventoryGoodsService; private final Map<String, Supplier<Consumer<Object>>> handlers = new HashMap <>(); @PostConstruct public void init () { handlers.put(Inventory.class.getSimpleName(), () -> iInventoryGoodsService::syncUpdateByInventory); handlers.put(Goods.class.getSimpleName(), () -> iInventoryGoodsService::syncUpdateByGoods); } @Override public void onApplicationEvent (ObserverEvent event) { String tableName = event.getTableName(); Object data = event.getData(); Supplier<Consumer<Object>> handlerSupplier = handlers.get(tableName); if (ObjectUtils.isEmpty(handlerSupplier)) { return ; } Consumer<Object> handler = handlerSupplier.get(); if (ObjectUtils.isEmpty(handler)) { return ; } handler.accept(data); } }
性能和维护的提升 使用观察者模式后的主要性能和维护优势包括 :
减少数据库查询次数 :通过及时更新中间表,可以避免每次查询时进行复杂的 JOIN
操作,从而提高查询效率
自动更新 :数据变化时自动通知相关的表,减少了手动同步数据的频率和复杂度
解耦设计 :观察者模式使得主题和观察者之间的依赖关系解耦,修改数据源时不需要修改中间表的实现,降低了维护成本
代码清晰 :通过将数据同步逻辑集中在观察者类中,代码变得更为清晰和易于管理。系统的扩展性和可维护性也得到了提升
优化观察者模式 在上一节中,我们虽然成功地使用继承自 ApplicationEvent
的 ObserverEvent
实现了观察者类,但每次需要通过观察者类来同步修改中间表时,都必须手动触发事件发布。这种方式显然繁琐、不方便且不够灵活。因此,接下来我们将通过使用拦截器、注解和 AOP
的方法来优化上一节中实现的观察者类。
自定义注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ObserverEventTrigger { Class<?>[] targetClasses() default {}; Class<?>[] excludedClasses() default {}; }
在我们的注解中,我们添加了两个可选参数,分别是 要筛选的类 和 要排除的类 ,这样可以使数据的处理更加灵活 实现更新数据持有器 1 2 3 4 5 6 7 8 9 10 11 public class UpdateDataHolder { public static final ThreadLocal<List<Object>> UPDATE_DATA = ThreadLocal.withInitial(ArrayList::new ); public static void remove () { UPDATE_DATA.remove(); } }
使用 ThreadLocal
存储和维护线程局部的数据列表 实现数据更新拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 @Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})}) public class DataUpdateInterceptor extends AbstractSqlParserHandler implements Interceptor { @Override @SneakyThrows public Object intercept (Invocation invocation) { Statement statement; Object firstArg = invocation.getArgs()[0 ]; if (Proxy.isProxyClass(firstArg.getClass())) { statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement" ); } else { statement = (Statement) firstArg; } MetaObject stmtMetaObj = SystemMetaObject.forObject(statement); try { statement = (Statement) stmtMetaObj.getValue("stmt.statement" ); } catch (Exception e) { } if (stmtMetaObj.hasGetter("delegate" )) { try { statement = (Statement) stmtMetaObj.getValue("delegate" ); } catch (Exception ignored) { } } String originalSql = statement.toString(); originalSql = originalSql.replaceAll("[\\s]+" , StringPool.SPACE); int index = indexOfSqlStart(originalSql); if (index > 0 ) { originalSql = originalSql.substring(index); } System.err.println("执行SQL:" + originalSql); StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement" ); String sql = originalSql.replace("where" , "WHERE" ); Collection<String> tables = new TableNameParser (sql).tables(); if (CollectionUtils.isEmpty(tables)) { return invocation.proceed(); } String tableName = tables.iterator().next(); TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName); Class<?> entityType = tableInfo.getEntityType(); SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(entityType); if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) { String selectSql = "AND " + sql.substring(sql.lastIndexOf("WHERE" ) + 5 ); Map<String, Object> map = new HashMap <>(1 ); map.put(Constants.WRAPPER, Wrappers.query().eq("1" , 1 ).last(selectSql)); SqlSession sqlSession = sqlSessionFactory.openSession(); try { List<?> updatedData = sqlSession.selectList(tableInfo.getSqlStatement(SqlMethod.SELECT_LIST.getMethod()), map); if (ObjectUtils.isNotEmpty(updatedData)) { UpdateDataHolder.UPDATE_DATA.get().add(updatedData); } } finally { SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); } } return invocation.proceed(); } @Override public void setProperties (Properties properties) { } private int indexOfSqlStart (String sql) { String upperCaseSql = sql.toUpperCase(); Set<Integer> set = new HashSet <>(); set.add(upperCaseSql.indexOf("SELECT " )); set.add(upperCaseSql.indexOf("UPDATE " )); set.add(upperCaseSql.indexOf("INSERT " )); set.add(upperCaseSql.indexOf("DELETE " )); set.remove(-1 ); if (CollectionUtils.isEmpty(set)) { return -1 ; } List<Integer> list = new ArrayList <>(set); list.sort(Comparator.naturalOrder()); return list.get(0 ); } }
该方法用于拦截 SQL
更新操作并将更新的处理数据存储到 ThreadLocal
中 配置拦截器 1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class MybatisPlusConfig { @Bean public ConfigurationCustomizer configurationCustomizer () { return configuration -> configuration.addInterceptor(new DataUpdateInterceptor ()); } }
将拦截器添加到 MyBatis-Plus 的配置中 实现观察者切面类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @Aspect @Component @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class ObserverAspect { private final ApplicationEventPublisher eventPublisher; @Pointcut("@annotation(observerEventTrigger)") public void observerPointcut (ObserverEventTrigger observerEventTrigger) { } @AfterReturning(value = "observerPointcut(observerEventTrigger)", argNames = "observerEventTrigger") public void afterDataUpdate (ObserverEventTrigger observerEventTrigger) { Class<?>[] targetClasses = observerEventTrigger.targetClasses(); Class<?>[] excludedClasses = observerEventTrigger.excludedClasses(); List<Object> objects = UpdateDataHolder.UPDATE_DATA.get(); if (ObjectUtils.isEmpty(objects)) { UpdateDataHolder.remove(); return ; } List<Object> finalObjects = new ArrayList <>(objects); Map<Class<?>, String> tableNameCache = new HashMap <>(finalObjects.size()); Set<Class<?>> excludedSet = new HashSet <>(Arrays.asList(excludedClasses)); Set<Class<?>> targetSet = new HashSet <>(Arrays.asList(targetClasses)); finalObjects.stream() .filter(item -> item instanceof List<?>) .map(item -> (List<?>) item) .filter(ObjectUtils::isNotEmpty) .forEach(list -> { Class<?> itemType = list.get(0 ).getClass(); boolean isTargetClass = ObjectUtils.isEmpty(targetSet) || targetSet.contains(itemType); boolean isExcludedClass = excludedSet.contains(itemType); if (isTargetClass && !isExcludedClass) { String tableName = tableNameCache.computeIfAbsent(itemType, this ::getTableName); eventPublisher.publishEvent(new ObserverEvent (this , list, tableName)); } }); UpdateDataHolder.remove(); } private String getTableName (Class<?> entityClass) { return entityClass.getSimpleName(); } }
该方法用于处理数据更新后的事件发布,即根据更新结果自动触发相应的事件处理逻辑。通过结合注解和切面,实现在数据更新后灵活的数据管理和事件发布机制。该方法采用了 stream
流的方式来处理数据,此外,还可以使用并行流或 for
循环的方式来处理,具体选择可以根据实际需求进行调整 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @Aspect @Component @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class ObserverAspect { private final ApplicationEventPublisher eventPublisher; @Pointcut("@annotation(observerEventTrigger)") public void observerPointcut (ObserverEventTrigger observerEventTrigger) { } @AfterReturning(value = "observerPointcut(observerEventTrigger)", argNames = "observerEventTrigger") public void afterDataUpdate (ObserverEventTrigger observerEventTrigger) { Class<?>[] targetClasses = observerEventTrigger.targetClasses(); Class<?>[] excludedClasses = observerEventTrigger.excludedClasses(); List<Object> objects = UpdateDataHolder.UPDATE_DATA.get(); if (ObjectUtils.isEmpty(objects)) { UpdateDataHolder.remove(); return ; } List<Object> finalObjects = new ArrayList <>(objects); Map<Class<?>, String> tableNameCache = new HashMap <>(finalObjects.size()); Set<Class<?>> excludedSet = new HashSet <>(Arrays.asList(excludedClasses)); Set<Class<?>> targetSet = new HashSet <>(Arrays.asList(targetClasses)); for (Object item : finalObjects) { if (item instanceof List<?>) { List<?> list = (List<?>) item; if (ObjectUtils.isNotEmpty(list)) { Class<?> itemType = list.get(0 ).getClass(); boolean isTargetClass = ObjectUtils.isEmpty(targetSet) || targetSet.contains(itemType); boolean isExcludedClass = excludedSet.contains(itemType); if (isTargetClass && !isExcludedClass) { String tableName = tableNameCache.computeIfAbsent(itemType, this ::getTableName); eventPublisher.publishEvent(new ObserverEvent (this , list, tableName)); } } } } UpdateDataHolder.remove(); } private String getTableName (Class<?> entityClass) { return entityClass.getSimpleName(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @Aspect @Component @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class ObserverAspect { private final ApplicationEventPublisher eventPublisher; @Pointcut("@annotation(observerEventTrigger)") public void observerPointcut (ObserverEventTrigger observerEventTrigger) { } @AfterReturning(value = "observerPointcut(observerEventTrigger)", argNames = "observerEventTrigger") public void afterDataUpdate (ObserverEventTrigger observerEventTrigger) { Class<?>[] targetClasses = observerEventTrigger.targetClasses(); Class<?>[] excludedClasses = observerEventTrigger.excludedClasses(); List<Object> objects = UpdateDataHolder.UPDATE_DATA.get(); if (ObjectUtils.isEmpty(objects)) { UpdateDataHolder.remove(); return ; } List<Object> finalObjects = new ArrayList <>(objects); Map<Class<?>, String> tableNameCache = new ConcurrentHashMap <>(finalObjects.size()); Set<Class<?>> excludedSet = new HashSet <>(Arrays.asList(excludedClasses)); Set<Class<?>> targetSet = new HashSet <>(Arrays.asList(targetClasses)); finalObjects.parallelStream() .filter(item -> item instanceof List<?>) .map(item -> (List<?>) item) .filter(ObjectUtils::isNotEmpty) .forEach(list -> { Class<?> itemType = list.get(0 ).getClass(); boolean isTargetClass = ObjectUtils.isEmpty(targetSet) || targetSet.contains(itemType); boolean isExcludedClass = excludedSet.contains(itemType); if (isTargetClass && !isExcludedClass) { String tableName = tableNameCache.computeIfAbsent(itemType, this ::getTableName); eventPublisher.publishEvent(new ObserverEvent (this , list, tableName)); } }); UpdateDataHolder.remove(); } private String getTableName (Class<?> entityClass) { return entityClass.getSimpleName(); } }
需要注意的是,并行流涉及多线程操作,因此必须保证线程安全。在并发环境中,HashMap
不具备线程安全性,多个线程同时修改它可能导致数据损坏或不一致。而 ConcurrentHashMap
是一个线程安全的集合类,它在设计时考虑了高并发场景,通过分段锁机制提升性能,使得多个线程可以并行访问不同的段,从而减少了锁竞争。 服务实现类使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Service @Transactional @RequiredArgsConstructor(onConstructor = @__(@Lazy)) public class GoodsServiceImpl extends ServiceImpl <GoodsMapper, Goods> implements IGoodsService { private final IInventoryService iInventoryService; @Override @ObserverEventTrigger public void updateGoods () { lambdaUpdate().eq(Goods::getId, "1234567890123456789" ) .set(Goods::getName, "商品D" ).update(); iInventoryService.lambdaUpdate().eq(Inventory::getId, "1111111111111111111" ) .set(Inventory::getQuantity, "300" ).update(); } }
该案例演示了注解的基本用法。默认情况下,注解会尝试对所有更新操作触发观察者事件。你也可以使用 targetClasses
参数来指定哪些类的更新操作需要触发事件,或者使用 excludedClasses
参数来排除某些类的更新操作。targetClasses
和 excludedClasses
是可选参数,你可以选择不使用、单独使用其中之一,或同时使用这两个参数。通过本方案,可以有效解决灵活性不足和操作繁琐的问题。 结语 在处理复杂的多表 JOIN
操作时,采用中间表和观察者模式显著提升了系统的性能和维护性。中间表通过减少复杂的 JOIN
操作,简化了查询语句,并提升了查询效率,减少了系统的计算负担。而观察者模式则通过自动同步数据,减少了手动更新的频率,确保了数据的一致性,同时增强了系统的灵活性和扩展性。
未来,我们还可以考虑其他优化方法,如使用表分区、缓存机制和数据仓库等,进一步提升系统的性能。此外,类似的优化策略也可以应用于实时数据处理、微服务架构和大数据环境中,以解决数据同步和性能优化的问题。