Conflicts:
	mybatis-flex-core/src/main/java/com/mybatisflex/core/service/IService.java
This commit is contained in:
开源海哥 2023-06-01 18:27:45 +08:00
commit 768f2be3ae
4 changed files with 398 additions and 139 deletions

View File

@ -78,6 +78,7 @@ export default defineConfig({
{text: '乐观锁', link: '/zh/core/version'},
{text: '数据填充', link: '/zh/core/fill'},
{text: '数据脱敏', link: '/zh/core/mask'},
{text: '数据缓存', link: '/zh/core/data-cache'},
{text: 'SQL 审计', link: '/zh/core/audit'},
{text: 'SQL 打印', link: '/zh/core/sql-print'},
{text: '多数据源', link: '/zh/core/multi-datasource'},

170
docs/zh/core/data-cache.md Normal file
View File

@ -0,0 +1,170 @@
# 数据缓存
MyBatis-Flex 是一个 MyBatis 增强框架,所以您可以使用 MyBatis 提供的二级缓存来作为数据缓存。但是它仍然有很多的缺点,比如不适用于分布式环境,在这里推荐使用 [Spring Cache](https://docs.spring.io/spring-framework/docs/5.2.24.RELEASE/spring-framework-reference/integration.html#cache) 模块来处理数据缓存。
## 使用方法
因为要用到 Spring Cache 模块,所以您的项目必须要使用 Spring Framework 框架,这里以 Spring Boot 项目作为例子,实现 MyBatis-Flex 项目将缓存数据存入 Redis 组件中。
1、引入 `spring-boot-starter-cache``spring-boot-starter-data-redis`模块
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
```
2、设置 Redis 连接信息(全是默认的话可以跳过这步)
```yaml
spring:
redis:
port: 6379
host: localhost
```
3、在 Spring Boot 配置类上启用 Spring Cache 缓存
```java
@EnableCaching
@Configuration
public class CacheConfig {
}
```
4、将 ServiceImpl 默认实现类换为 CacheableServiceImpl 实现类
```java
public interface AccountService extends IService<Account> {
}
@Service
public class AccountServiceImpl extends CacheableServiceImpl<AccountMapper, Account> implements AccountService {
}
```
5、最后即可使用 Spring Cache 的相关注解实现数据缓存到 Redis 中了
```java
// 设置统一的缓存名称
@Service
@CacheConfig(cacheNames = "account")
public class AccountServiceImpl extends CacheableServiceImpl<AccountMapper, Account> implements AccountService {
// 根据主键缓存数据
@Override
@Cacheable(key = "#id")
public Account getById(Serializable id) {
return super.getById(id);
}
// 根据方法名加查询 SQL 语句缓存结果数据
// 加上方法名是为了避免不同的方法使用一样的 QueryWrapper
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public List<Account> list(QueryWrapper query) {
return super.list(query);
}
}
```
## 使用说明
MyBatis-Flex 在 IService 接口中做了方法调用链优化,所以您只需将缓存注解加到一些特定的方法上,即可实现所有相关的方法也可以进行数据缓存。相关方法见如下示例:
```java
@Service
@CacheConfig(cacheNames = "account")
public class AccountServiceImpl extends CacheableServiceImpl<MyAccountMapper, Account> {
@Override
@CacheEvict(allEntries = true)
public boolean remove(QueryWrapper query) {
return super.remove(query);
}
@Override
@CacheEvict(key = "#id")
public boolean removeById(Serializable id) {
return super.removeById(id);
}
@Override
@CacheEvict(allEntries = true)
public boolean removeByIds(Collection<? extends Serializable> ids) {
return super.removeByIds(ids);
}
@Override
@CachePut(key = "#entity.id")
public boolean update(Account entity, QueryWrapper query) {
return super.update(entity, query);
}
@Override
@CachePut(key = "#entity.id")
public boolean updateById(Account entity) {
return super.updateById(entity);
}
@Override
@Cacheable(key = "#id")
public Account getById(Serializable id) {
return super.getById(id);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public Account getOne(QueryWrapper query) {
return super.getOne(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public <R> R getOneAs(QueryWrapper query, Class<R> asType) {
return super.getOneAs(query, asType);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public List<Account> list(QueryWrapper query) {
return super.list(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public <R> List<R> listAs(QueryWrapper query, Class<R> asType) {
return super.listAs(query, asType);
}
// 无法通过注解进行缓存操作
@Override
@Deprecated
public List<Account> listByIds(Collection<? extends Serializable> ids) {
return super.listByIds(ids);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public long count(QueryWrapper query) {
return super.count(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toDebugSQL()")
public Page<Account> page(Page<Account> page, QueryWrapper query) {
return super.page(page, query);
}
}
```

View File

@ -1,26 +1,26 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.service;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.RowMapper;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.SqlUtil;
@ -36,20 +36,22 @@ import java.util.*;
* @author 王帅
* @since 2023-05-01
*/
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "unchecked"})
public interface IService<T> {
int DEFAULT_BATCH_SIZE = 1000;
// ===== 保存操作 =====
/**
* 获取对应实体类Entity的基础映射类BaseMapper
* <p>获取对应实体类Entity的基础映射类BaseMapper
*
* @return 基础映射类BaseMapper
*/
BaseMapper<T> getMapper();
/**
* 保存实体类对象数据
* <p>保存实体类对象数据
*
* @param entity 实体类对象
* @return {@code true} 保存成功{@code false} 保存失败
@ -60,9 +62,8 @@ public interface IService<T> {
return SqlUtil.toBool(getMapper().insertSelective(entity));
}
/**
* 保存或者更新实体类对象数据
* <p>保存或者更新实体类对象数据
*
* @param entity 实体类对象
* @return {@code true} 保存或更新成功{@code false} 保存或更新失败
@ -73,30 +74,30 @@ public interface IService<T> {
}
/**
* 批量保存实体类对象数据
* <p>批量保存实体类对象数据
*
* @param entities 实体类对象
* @return {@code true} 保存成功{@code false} 保存失败
*/
default boolean saveBatch(Collection<T> entities) {
return SqlUtil.toBool(getMapper().insertBatch(new ArrayList<>(entities)));
return saveBatch(entities, DEFAULT_BATCH_SIZE);
}
// ===== 删除操作 =====
/**
* 批量保存实体类对象数据
* <p>批量保存实体类对象数据
*
* @param entities 实体类对象
* @param size 每次保存切分的数量
* @param entities 实体类对象
* @param batchSize 每次保存切分的数量
* @return {@code true} 保存成功{@code false} 保存失败
*/
default boolean saveBatch(Collection<T> entities, int size) {
return SqlUtil.toBool(getMapper().insertBatch(CollectionUtil.toList(entities), size));
default boolean saveBatch(Collection<T> entities, int batchSize) {
return SqlUtil.toBool(getMapper().insertBatch(CollectionUtil.toList(entities), batchSize));
}
/**
* 根据查询条件删除数据
* <p>根据查询条件删除数据
*
* @param query 查询条件
* @return {@code true} 删除成功{@code false} 删除失败
@ -106,17 +107,17 @@ public interface IService<T> {
}
/**
* 根据查询条件删除数据
* <p>根据查询条件删除数据
*
* @param condition 查询条件
* @return {@code true} 删除成功{@code false} 删除失败
*/
default boolean remove(QueryCondition condition) {
return SqlUtil.toBool(getMapper().deleteByCondition(condition));
return remove(query().where(condition));
}
/**
* 根据数据主键删除数据
* <p>根据数据主键删除数据
*
* @param id 数据主键
* @return {@code true} 删除成功{@code false} 删除失败
@ -126,7 +127,7 @@ public interface IService<T> {
}
/**
* 根据数据主键批量删除数据
* <p>根据数据主键批量删除数据
*
* @param ids 数据主键
* @return {@code true} 删除成功{@code false} 删除失败
@ -141,40 +142,21 @@ public interface IService<T> {
// ===== 更新操作 =====
/**
* 根据 {@link Map} 构建查询条件删除数据
* <p>根据 {@link Map} 构建查询条件删除数据
*
* @param query 查询条件
* @return {@code true} 删除成功{@code false} 删除失败
*/
default boolean removeByMap(Map<String, Object> query) {
return SqlUtil.toBool(getMapper().deleteByMap(query));
}
/**
* 更加主键更新 entity默认忽略 null
*
* @param entity 实体类对象
* @return {@code true} 更新成功{@code false} 更新失败
*/
default boolean updateById(T entity) {
return updateById(entity, true);
}
/**
* 更加主键更新 entity
*
* @param entity entity
* @param ignoreNulls 是否忽略 null
* @return {@code true} 更新成功{@code false} 更新失败
*/
default boolean updateById(T entity, boolean ignoreNulls) {
return SqlUtil.toBool(getMapper().update(entity, ignoreNulls));
// 防止全表删除
if (query == null || query.isEmpty()) {
throw FlexExceptions.wrap("deleteByMap is not allow empty map.");
}
return remove(query().where(query));
}
/**
* 根据查询条件更新数据
* <p>根据查询条件更新数据
*
* @param entity 实体类对象
* @param query 查询条件
@ -184,62 +166,69 @@ public interface IService<T> {
return SqlUtil.toBool(getMapper().updateByQuery(entity, query));
}
/**
* 根据 {@link Map} 构建查询条件更新数据
*
* @param entity 实体类对象
* @param query 查询条件
* @return {@code true} 更新成功{@code false} 更新失败
*/
default boolean update(T entity, Map<String, Object> query) {
return SqlUtil.toBool(getMapper().updateByMap(entity, query));
}
/**
* 根据查询条件更新数据
* <p>根据查询条件更新数据
*
* @param entity 实体类对象
* @param condition 查询条件
* @return {@code true} 更新成功{@code false} 更新失败
*/
default boolean update(T entity, QueryCondition condition) {
return SqlUtil.toBool(getMapper().updateByCondition(entity, condition));
return update(entity, query().where(condition));
}
/**
* 根据 id 批量更新数据
* <p>根据数据主键批量更新数据
*
* @param entities 实体类对象集合
* @return boolean {@code true} 更新成功{@code false} 更新失败
*/
default boolean updateBatch(Collection<T> entities) {
return updateBatch(entities, RowMapper.DEFAULT_BATCH_SIZE);
return updateBatch(entities, DEFAULT_BATCH_SIZE);
}
/**
* 根据 id 批量更新数据
* <p>根据数据主键批量更新数据
*
* @param entities 实体类对象集合
* @param batchSize 每批次更新数量
* @return boolean {@code true} 更新成功{@code false} 更新失败
* @return {@code true} 更新成功{@code false} 更新失败
*/
@SuppressWarnings("unchecked")
default boolean updateBatch(Collection<T> entities, int batchSize) {
return Db.tx(() -> {
final List<T> entityList = CollectionUtil.toList(entities);
// BaseMapper 是经过 Mybatis 动态代理处理过的对象需要获取原始 BaseMapper 类型
final Class<BaseMapper<T>> usefulClass = (Class<BaseMapper<T>>) ClassUtil.getUsefulClass(getMapper().getClass());
return SqlUtil.toBool(Arrays.stream(Db.executeBatch(entityList.size(), batchSize, usefulClass, (mapper, index) -> mapper.update(entityList.get(index)))).sum());
});
}
/**
* <p>根据数据主键更新数据
*
* @param entity 实体类对象
* @return {@code true} 更新成功{@code false} 更新失败
*/
default boolean updateById(T entity) {
return SqlUtil.toBool(getMapper().update(entity));
}
/**
* <p>根据 {@link Map} 构建查询条件更新数据
*
* @param entity 实体类对象
* @param query 查询条件
* @return {@code true} 更新成功{@code false} 更新失败
*/
default boolean updateByMap(T entity, Map<String, Object> query) {
return update(entity, query().where(query));
}
// ===== 查询操作 =====
/**
* 根据数据主键查询一条数据
* <p>根据数据主键查询一条数据
*
* @param id 数据主键
* @return 查询结果数据
@ -249,7 +238,7 @@ public interface IService<T> {
}
/**
* 根据数据主键查询一条数据
* <p>根据数据主键查询一条数据
*
* @param id 数据主键
* @return 查询结果数据
@ -260,7 +249,7 @@ public interface IService<T> {
}
/**
* 根据查询条件查询一条数据
* <p>根据查询条件查询一条数据
*
* @param query 查询条件
* @return 查询结果数据
@ -269,9 +258,19 @@ public interface IService<T> {
return getMapper().selectOneByQuery(query);
}
/**
* <p>根据查询条件查询一条数据
*
* @param query 查询条件
* @return 查询结果数据
* @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回方便链式操作
*/
default Optional<T> getOneOpt(QueryWrapper query) {
return Optional.ofNullable(getOne(query));
}
/**
* 根据查询条件查询一条数据并通过 asType 进行接收
* <p>根据查询条件查询一条数据并通过 asType 进行接收
*
* @param query 查询条件
* @param asType 接收的数据类型
@ -282,41 +281,29 @@ public interface IService<T> {
}
/**
* 根据查询条件查询一条数据
*
* @param query 查询条件
* @return 查询结果数据
* @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回方便链式操作
*/
default Optional<T> getOneOpt(QueryWrapper query) {
return Optional.ofNullable(getOne(query));
}
/**
* 根据查询条件查询一条数据
* <p>根据查询条件查询一条数据
*
* @param query 查询条件
* @param asType 接收的数据类型
* @return 查询结果数据
* @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回方便链式操作
*/
default <R> Optional<R> getOneOptAs(QueryWrapper query, Class<R> asType) {
default <R> Optional<R> getOneAsOpt(QueryWrapper query, Class<R> asType) {
return Optional.ofNullable(getOneAs(query, asType));
}
/**
* 根据查询条件查询一条数据
* <p>根据查询条件查询一条数据
*
* @param condition 查询条件
* @return 查询结果数据
*/
default T getOne(QueryCondition condition) {
return getMapper().selectOneByCondition(condition);
return getOne(query().where(condition));
}
/**
* 根据查询条件查询一条数据
* <p>根据查询条件查询一条数据
*
* @param condition 查询条件
* @return 查询结果数据
@ -327,37 +314,16 @@ public interface IService<T> {
}
/**
* 查询所有数据
* <p>查询所有数据
*
* @return 所有数据
*/
default List<T> list() {
return getMapper().selectAll();
return list(query());
}
/**
* 根据查询条件查询数据集合
*
* @param condition 查询条件
* @return 数据集合
*/
default List<T> list(QueryCondition condition) {
return getMapper().selectListByCondition(condition);
}
/**
* 根据查询条件查询数据集合
*
* @param condition 查询条件
* @return 数据集合
*/
default List<T> list(QueryCondition condition, int count) {
return getMapper().selectListByCondition(condition, count);
}
/**
* 根据查询条件查询数据集合
* <p>根据查询条件查询数据集合
*
* @param query 查询条件
* @return 数据集合
@ -367,7 +333,17 @@ public interface IService<T> {
}
/**
* 根据查询条件查询数据集合并通过 asType 进行接收
* <p>根据查询条件查询数据集合
*
* @param condition 查询条件
* @return 数据集合
*/
default List<T> list(QueryCondition condition) {
return list(query().where(condition));
}
/**
* <p>根据查询条件查询数据集合并通过 asType 进行接收
*
* @param query 查询条件
* @param asType 接收的数据类型
@ -377,9 +353,8 @@ public interface IService<T> {
return getMapper().selectListByQueryAs(query, asType);
}
/**
* 根据数据主键查询数据集合
* <p>根据数据主键查询数据集合
*
* @param ids 数据主键
* @return 数据集合
@ -389,19 +364,19 @@ public interface IService<T> {
}
/**
* 根据 {@link Map} 构建查询条件查询数据集合
* <p>根据 {@link Map} 构建查询条件查询数据集合
*
* @param query 查询条件
* @return 数据集合
*/
default List<T> listByMap(Map<String, Object> query) {
return getMapper().selectListByMap(query);
return list(query().where(query));
}
// ===== 数量查询操作 =====
/**
* 根据查询条件判断数据是否存在
* <p>根据查询条件判断数据是否存在
*
* @param query 查询条件
* @return {@code true} 数据存在{@code false} 数据不存在
@ -411,7 +386,7 @@ public interface IService<T> {
}
/**
* 根据查询条件判断数据是否存在
* <p>根据查询条件判断数据是否存在
*
* @param condition 查询条件
* @return {@code true} 数据存在{@code false} 数据不存在
@ -421,16 +396,16 @@ public interface IService<T> {
}
/**
* 查询所有数据数量
* <p>查询所有数据数量
*
* @return 所有数据数量
*/
default long count() {
return getMapper().selectCountByQuery(QueryWrapper.create());
return count(query());
}
/**
* 根据查询条件查询数据数量
* <p>根据查询条件查询数据数量
*
* @param query 查询条件
* @return 数据数量
@ -440,29 +415,29 @@ public interface IService<T> {
}
/**
* 根据查询条件查询数据数量
* <p>根据查询条件查询数据数量
*
* @param condition 查询条件
* @return 数据数量
*/
default long count(QueryCondition condition) {
return getMapper().selectCountByCondition(condition);
return count(query().where(condition));
}
// ===== 分页查询操作 =====
/**
* 分页查询所有数据
* <p>分页查询所有数据
*
* @param page 分页对象
* @return 分页对象
*/
default Page<T> page(Page<T> page) {
return getMapper().paginate(page, QueryWrapper.create());
return page(page, query());
}
/**
* 根据查询条件分页查询数据
* <p>根据查询条件分页查询数据
*
* @param page 分页对象
* @param query 查询条件
@ -473,14 +448,18 @@ public interface IService<T> {
}
/**
* 根据查询条件分页查询数据
* <p>根据查询条件分页查询数据
*
* @param page 分页对象
* @param condition 查询条件
* @return 分页对象
*/
default Page<T> page(Page<T> page, QueryCondition condition) {
return getMapper().paginate(page, QueryWrapper.create().where(condition));
return page(page, query().where(condition));
}
default QueryWrapper query() {
return QueryWrapper.create();
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.spring.service.impl;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.service.IService;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
/**
* <p>可缓存数据的 Service 实现类
*
* <p>该实现类对缓存做了以下处理
*
* <ul>
* <li>重写 {@link #saveOrUpdate(Object)} 方法分别调用 {@link #save(Object)} {@link #updateById(Object)}
* 方法避免缓存无法更新造成数据不一致
* <li>重写{@link #updateBatch(Collection, int)} 方法默认抛出异常不支持批量更新操作
* 防止批量更新数据缓存不一致
* <li>重写 {@link #query()} 方法解决使用 {@link QueryWrapper#toDebugSQL()} 作为缓存
* 的主键时"SELECT * FROM" 后面没有表名的问题
* </ul>
*
* @author 王帅
* @since 2023-05-30
*/
public class CacheableServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
@Autowired
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
private M mapper;
/**
* {@inheritDoc}
*/
@Override
public BaseMapper<T> getMapper() {
return mapper;
}
/**
* {@inheritDoc}
*/
@Override
public boolean saveOrUpdate(T entity) {
TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass());
Object[] pkArgs = tableInfo.buildPkSqlArgs(entity);
if (pkArgs.length == 0 || pkArgs[0] == null) {
return save(entity);
} else {
return updateById(entity);
}
}
/**
* <p>不支持批量更新操作
*/
@Override
public boolean updateBatch(Collection<T> entities, int batchSize) {
throw FlexExceptions.wrap("Batch update do not support caching operation.");
}
/**
* <p>获取默认的 {@link QueryWrapper}
*
* <p>使用 {@link QueryWrapper#create()} 构建默认查询条件的时候
* 要使用 {@link QueryWrapper#from(String...)} 方法指定从哪个表
* 查询数据不然使用 {@link QueryWrapper#toDebugSQL()} 生成的
* SQL 语句就是 {@code "SELECT * FROM"}没有表名信息
*
* <p>默认通过反射获取表名建议重写根据情况设置默认表名以提升效率
*
* <p>例如
*
* <pre>{@code
* @Override
* public QueryWrapper query() {
* return QueryWrapper.create().from(ACCOUNT);
* }
* }</pre>
*
* @return 默认的 {@link QueryWrapper}
*/
@Override
public QueryWrapper query() {
String tableName = TableInfoFactory.ofMapperClass(getMapper().getClass()).getTableName();
return QueryWrapper.create().from(tableName);
}
}