This commit is contained in:
2019-08-15 01:25:05 +08:00
parent 3c56288991
commit 7242f6d76c
74 changed files with 4375 additions and 126 deletions
+35
View File
@@ -0,0 +1,35 @@
# zilean-mybatis
简化单表重复的CRUD操作,有了它,无需手动建表,简单查询无需重复书写XML。封装隔离后,可轻松嵌入到任何系统里,目前仅针对MySQL有效
### 特别说明
```txt
封装隔离后,可轻松嵌入到任何系统里,目前仅针对MySQL有效
```
### 引用Jar
```txt
Lombok ---------------- 简化get/set/toString等方法, https://www.projectlombok.org/
Spring Data JPA ------- Java持久层API
MySQL Driver ---------- MySQL数据库驱动
MyBatis Framework ----- Mybatis框架
HuTool ---------------- 贼好用的Java工具类库
Druid ----------------- 阿里数据库连接池组件
Fastjson -------------- 阿里Json工具类
```
### 主要功能
```txt
简化单表crud操作, 基础方法无需书写xml也无需引用mybatis的crud注解
运用JPA实现自动建表, 你再也不用烦恼, 表字段究竟怎么取名才好
```
### 注意
```txt
1、BaseEntity子类必须有javax.persistence.Entity, javax.persistence.Table两个注解
2、BaseEntity子类中的@Table必须含有name属性, 并指定表名, 不然会导致建表失败, 并且BaseMapper也取不到表名
3、BaseDao子接口必须有org.springframework.stereotype.Repository注解
4、根据阿里巴巴Java编码规范, BaseEntity子类中不能出现不属于数据库表的字段, 如需关联查询, 请新建BaseVo的子类
5、建议简单的crud使用BaseDao中的方法, 复杂的关联查询使用xml
6、如果不需要, 或者不怎么会用Lombok的话, 只需要从pom中移除Lombok依赖, 在对应Lombok注解处加上get/set方法即可
```
+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- maven可以继承父项目中的参数,所以可以删除子模块中相同定义 -->
<artifactId>zilean-mybatis</artifactId>
<packaging>jar</packaging>
<name>zilean-mybatis</name>
<description>简易MyBatis模块</description>
<parent>
<groupId>cn.odboy</groupId>
<artifactId>zilean</artifactId>
<version>1.0.0</version>
<!-- lookup parent from repository -->
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<!-- maven可以继承父项目中的依赖,所以可以删除子模块中相同定义 -->
<!--lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- HuTool工具箱-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--jpa 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${springboot.version}</version>
</dependency>
<!--Spring-Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- JSON解析 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- zilean-common 依赖 -->
<dependency>
<groupId>cn.odboy</groupId>
<artifactId>zilean-common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<!-- 如果其他包需要依赖这个模块那么就不要加这个 -->
<!--<build>-->
<!--<plugins>-->
<!--<plugin>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-maven-plugin</artifactId>-->
<!--</plugin>-->
<!--</plugins>-->
<!--</build>-->
</project>
@@ -0,0 +1,133 @@
package cn.odboy.zilean.mybatis.cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.WeakCache;
import cn.hutool.core.date.DateUnit;
import cn.odboy.zilean.mybatis.constant.SqlConsts;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author 田俊
* @version 1.0.0
* @name: DefaultMyBatisCache <br>
* @description: TODO 自定义的Mybatis缓存 <br>
* @date: 2019/04/2019/4/6 <br>
* @since JDK 1.8
*/
public final class DefaultMyBatisCache implements Cache {
private static final Logger log = LoggerFactory.getLogger(DefaultMyBatisCache.class);
private String id;
private ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* 过期时间:30分钟
*/
private WeakCache<Object, Object> cache =
CacheUtil.newWeakCache(DateUnit.MINUTE.getMillis() * 30);
public DefaultMyBatisCache() {
log.debug("Mybatis自定义缓存初始化");
}
/**
* 必须有该构造函数
*
* @param id
*/
public DefaultMyBatisCache(String id) {
log.debug("Mybatis自定义缓存通过ID初始化");
this.id = id;
}
/**
* 获取缓存编号
*
* @return String
*/
@Override
public String getId() {
log.debug("得到ID:" + id);
return id;
}
/**
* 获取缓存对象的大小
*
* @return int
*/
@Override
public int getSize() {
log.debug("获取缓存大小!");
return cache.size();
}
/**
* 保存key值缓存对象
*
* @param key
* @param value
*/
@Override
public void putObject(Object key, Object value) {
log.debug("往缓存中添加元素:key=" + key + ",value=" + value);
cache.put(key, value);
}
/**
* 通过KEY获取缓存对象
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
if (key != null && (SqlConsts.PH_SQL_FIND.contains(String.valueOf(key)))) {
// 二级缓存不更新问题
cache.remove(key);
}
log.debug("通过kEY获取值:" + key);
log.debug("OVER");
log.debug("=======================================================");
log.debug("值为:" + cache.get(key));
log.debug("=====================OVER==============================");
return cache.get(key);
}
/**
* 通过key删除缓存对象
*
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
log.debug("移除缓存对象:" + key);
cache.remove(key);
return key;
}
/**
* 清空缓存
*/
@Override
public void clear() {
log.debug("清除缓存!");
cache.clear();
}
/**
* 获取缓存的读写锁
*
* @return
*/
@Override
public ReadWriteLock getReadWriteLock() {
log.debug("获取锁对象!!!");
return lock;
}
}
@@ -0,0 +1,61 @@
package cn.odboy.zilean.mybatis.constant;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO SQL常量
* @time: 2019/8/8 10:41
* @blog: www.odboy.cn
*/
public class SqlConsts {
/**
* 对象属性:主键名称
*/
public static final String PRIMARY_KEY_NAME = "id";
/**
* 方法名称:获取主键, 具体查看BaseEntity中的属性名称
*/
public static final String GET_ID = "getId";
/**
* 方法名称:设置更新时间, 具体查看BaseEntity中的属性名称
*/
public static final String SET_UPDATE_TIME = "setUpdateTime";
/**
* 错误提示:当Entity类没有标注注解@Table(javax.persistence.Table)时, EntityMapper继承了BaseMapper后报错
*/
public static final String TX_TABLE_NONE =
"POJO类中没有@Table注解, 不能使用BaseMapper接口, 如需使用BaseMapper接口, 请加上注解javax.persistence.Table";
/**
* 错误提示:当分页对象 Page 为null时的提示
*/
public static final String TX_PAGE_NULL = "分页对象Page不能为空";
/**
* SQL条件表达式:有效数据
*/
public static final String EX_SQL_EFFECTIVE_DATA = "delete_flag=0";
/**
* SQL条件表达式:无效数据
*/
public static final String EX_SQL_UNEFFECTIVE_DATA = "delete_flag=1";
/**
* SQL条件表达式:通用统计
*/
public static final String EX_SQL_CUSTOM_COUNT = "count(0)";
/**
* SQL条件表达式:分页
*/
public static final String PR_SQL_PAGE = " LIMIT {}, {}";
/**
* SQL参数字段:创建时间、主键
*/
public static final String PR_SQL_ORDER_ID = "create_time desc";
/**
* SQL查找方法名称头
*/
public static final String PH_SQL_FIND = "find";
/**
* GET
*/
public static final String PH_SQL_GET = "get";
}
@@ -0,0 +1,121 @@
package cn.odboy.zilean.mybatis.core;
import cn.odboy.zilean.mybatis.entity.BaseEntity;
import cn.odboy.zilean.mybatis.page.Page;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO 通用Dao接口
* <p>MyBatis提供了2个注解标记:@Select(只能处理字符串常量) 和 @SelectProvider(可以使用一个SQL提供程序来生成相应的SQL语句)
* <p>这几个注解声明在Mapper对应的interface的方法上的,注解用于生成查询用的sql语句
* 如果对应的Mapper中已使用@Param来注解参数,则在对应的Prodiver的方法中无需写参数
* <p>注解中的参数:  type参数指定的Class类,必须要能够通过无参的构造函数来初始化
* method参数指定的方法,必须是public的,返回值必须为String,可以为static
* <p>重点:在Mapper接口和@SelectProvide方法类中,不要使用重载,也就是说,不要使用方法名相同参数不同的方法
* @time: 2019/8/8 10:41
* @blog: www.odboy.cn
*/
public interface BaseDao<T extends BaseEntity> {
/**
* 选择性插入(非空属性值), 并返回受影响的行数
* <p>
* 插入后,可获取到id的值
*
* @param entity 实体对象
* @return 受影响的行数
*/
@InsertProvider(type = SqlBuilder.class, method = "insertSelective")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertSelective(T entity);
/**
* 通过ID删除, 并返回受影响的行数
*
* @param id 主键ID
* @return 受影响的行数
*/
@UpdateProvider(type = SqlBuilder.class, method = "deleteById")
int deleteById(Long id);
/**
* 通过ID更新, 并返回受影响的行数
*
* @param entity 实体对象
* @return 受影响的行数
*/
@UpdateProvider(type = SqlBuilder.class, method = "updateSelectiveById")
int updateSelectiveById(T entity);
/**
* 根据ID查询, 并返回单个实体对象
* 注解ResultMap的意思是从sql的执行结果集中取数据,拼装成Enitity的实体类
* 当mapper中传入的参数是使用@param
* 注解修饰,在xxxProvider类中必须使用Map对象接收参数
*
* @param id 主键ID
* @return 实体对象
*/
@SelectProvider(type = SqlBuilder.class, method = "selectOneById")
T selectOneById(Long id);
/**
* 通过类属性和值查询,并返回实体对象集合
*
* @param nav 类属性名称和值的容器
* @return 实体对象集合
*/
@SelectProvider(type = SqlBuilder.class, method = "selectByNav")
List<T> selectByNav(@Param("nav") EntityPropNav nav);
/**
* 通过类属性和值模糊查询,并返回实体对象集合
*
* @param nav 类属性名称和值的容器
* @return 实体对象集合
*/
@SelectProvider(type = SqlBuilder.class, method = "selectByLikeNav")
List<T> selectByLikeNav(@Param("nav") EntityPropNav nav);
/**
* 分页获取对象, 并返回实体对象集合
*
* @param entity 查询对象
* @param page 分页对象
* @return 实体对象集合
*/
@SelectProvider(type = SqlBuilder.class, method = "selectByPage")
List<T> selectByPage(@Param("entity") T entity, @Param("page") Page page);
/**
* 不分页, 查询所有(存在全表查询风险,不建议大表与超大表使用)
*
* @param entity 查询对象entity
* @return 实体对象集合
*/
@SelectProvider(type = SqlBuilder.class, method = "selectAll")
List<T> selectAll(@Param("entity") T entity);
/**
* 不分页获取范围内对象, 并返回实体对象集合(这个估计会有点bug,如果查询字符串得加单引号不然mybatis会以为它是数据库字段名称)
*
* @param propName 对象属性名称
* @param propValues 对象属性值集合
* @return 实体对象集合
*/
@SelectProvider(type = SqlBuilder.class, method = "selectAllInValues")
List<T> selectAllInValues(@Param("propName") String propName, @Param("propValues") List<Object> propValues);
/**
* 条件统计数量
*
* @param entity 查询对象entity
* @return 总数量
*/
@SelectProvider(type = SqlBuilder.class, method = "countAll")
long countAll(@Param("entity") T entity);
}
@@ -0,0 +1,69 @@
package cn.odboy.zilean.mybatis.core;
import cn.hutool.core.util.ObjectUtil;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import java.io.Serializable;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO 对象属性名称和值(容器)
* @time: 2019/8/10 3:37
* @blog: www.odboy.cn
*/
@Getter
@ToString
@Log4j2
public class EntityPropNav implements Serializable {
private static EntityPropNav cloneObj = new EntityPropNav();
/**
* 对象属性名称
*/
public String name;
/**
* 对象属性值
*/
public Object value;
/**
* 私有化构造函数,不得从外部new
*/
private EntityPropNav() {
}
/**
* 获取对象克隆实例, 适合频繁new的场景
*
* @param name 对象属性名称
* @param value 对象属性值
* @return EntityPropNav
*/
public static EntityPropNav build(String name, Object value) {
return ObjectUtil.cloneByStream(cloneObj).setName(name).setValue(value);
}
/**
* 设置对象属性名称
*
* @param name 对象属性名称
*/
public EntityPropNav setName(String name) {
this.name = name;
return this;
}
/**
* 设置对象属性值
*
* @param value 对象属性值
*/
public EntityPropNav setValue(Object value) {
this.value = value;
return this;
}
}
@@ -0,0 +1,445 @@
package cn.odboy.zilean.mybatis.core;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.odboy.zilean.common.constant.GlobalConsts;
import cn.odboy.zilean.mybatis.constant.SqlConsts;
import cn.odboy.zilean.mybatis.entity.BaseEntity;
import cn.odboy.zilean.mybatis.page.Page;
import cn.odboy.zilean.mybatis.util.BeanColumnUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.builder.annotation.ProviderContext;
import org.apache.ibatis.jdbc.SQL;
import org.springframework.core.annotation.AnnotationUtils;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO SQL构造器
* @time: 2019/8/8 10:35
* @blog: www.odboy.cn
*/
@Log4j2
public class SqlBuilder<T extends BaseEntity> {
/**
* 表名
*/
private String tableName = null;
/**
* 表字段映射
*/
private String baseColumnList = null;
/**
* Bean属性名称
*/
private List<String> beanNames = new ArrayList<>(GlobalConsts.DEFAULT_COLLECTIONS_SIZE);
/**
* Bean属性名称-->数据库表名 映射关系
*/
private Map<String, String> beanTableMap = new HashMap<>(GlobalConsts.DEFAULT_COLLECTIONS_SIZE);
/**
* 数据库表名-->get方法名称
*/
private Map<String, String> tableGetNameMap = new HashMap<>(GlobalConsts.DEFAULT_COLLECTIONS_SIZE);
/**
* 初始化
*
* @param context ProviderContext
* @throws Exception
*/
private void init(ProviderContext context) throws Exception {
if (StrUtil.isBlank(tableName)) {
if (!initTableName(context)) {
throw new RuntimeException(SqlConsts.TX_TABLE_NONE);
}
}
}
/**
* 获取并设置表名
*
* @param context ProviderContext
* @return boolean
*/
private boolean initTableName(ProviderContext context) throws Exception {
Class mapperType = context.getMapperType();
// 强制转换为“参数化类型” [BaseDao<T extends BaseEntity>]
ParameterizedType genericSuperclass = (ParameterizedType) mapperType.getGenericInterfaces()[0];
// Class类是Type接口的实现类
// 获取参数化类型中,实际类型的定义 [new Type[]{T.class}]
// 获取数据的第一个元素:T.class
// 当前运行类的参数化类型中的实际的类型, 比如BaseMapper<User>, 此处的Class为User.class
Class clazz = (Class) genericSuperclass.getActualTypeArguments()[0];
// 获取表名
// 当entity类有注解时
// @javax.persistence.Table(name=sso_user, ...), 自动建表的表名称不得以"t_"或其他前缀开头,只能是类名下换线命名
// 没有注解, 返回null
Table table = AnnotationUtils.getAnnotation(clazz, Table.class);
if (table != null) {
this.tableName = table.name();
// 获取bean属性名称
// 根据jpa内定策略转换为表字段名称
Field[] fields = ReflectUtil.getFields(clazz);
String sqlColumnName, fieldName;
for (Field field : fields) {
fieldName = field.getName();
sqlColumnName = BeanColumnUtils.getDataBaseColumnName(fieldName);
// 建立映射关系
beanNames.add(fieldName);
beanTableMap.put(fieldName, sqlColumnName);
tableGetNameMap.put(sqlColumnName, SqlConsts.PH_SQL_GET + BeanColumnUtils.toUpperCaseFirstOne(fieldName));
// 设置查询字段
// 定义临时变量
StringBuilder column = new StringBuilder();
// fix bug: 20190703 由于驼峰命名的原因导致查出来的数据不能设置到实体类中
for (Map.Entry<String, String> entry : this.beanTableMap.entrySet()) {
// value, 即实体类对应的表字段名称
column.append(entry.getValue()).append(" as ");
// value, 即实体类本身的属性名称
column.append(entry.getKey()).append(",");
}
// 去除末尾的逗号
this.baseColumnList = column.substring(0, column.length() - 1);
}
return true;
}
return false;
}
/**
* 选择性插入
*
* @param context 上下文
* @param entity <T extends BaseEntity>
* @return SQL
*/
public String insertSelective(ProviderContext context, T entity) throws Exception {
this.init(context);
if (entity == null) {
throw new RuntimeException("entity不能为null");
}
// 非空属性数量
int nonNullPropSize = 0;
// 参数字段 有序合集
List<String> columns = new ArrayList<>(GlobalConsts.DEFAULT_COLLECTIONS_SIZE);
// 参数字段值 有序合集
List<String> values = new ArrayList<>(GlobalConsts.DEFAULT_COLLECTIONS_SIZE);
List<String> nonNullList = getNonNullList(entity, beanNames, tableGetNameMap, beanTableMap);
for (String beanName : nonNullList) {
// 此处不能用nonNullPropSize++, ++操作符是线程不安全的
nonNullPropSize = nonNullPropSize + 1;
// 获取属性名称
columns.add(beanTableMap.get(beanName));
values.add("#{" + beanName + "}");
}
// 判断是不是什么都没做
if (nonNullPropSize == 0) {
throw new RuntimeException("至少有一个字段有值!");
}
// 组装SQL
SQL sql = new SQL();
sql.INSERT_INTO(SqlBuilder.this.tableName);
sql.INTO_COLUMNS(columns.toArray(new String[0]));
sql.INTO_VALUES(values.toArray(new String[0]));
log.info("SQL -> " + sql.toString());
log.info("Entity -> " + JSON.toJSONString(entity));
return sql.toString();
}
/**
* 通过ID(逻辑)删除
*
* @param context 上下文
* @param id 主键值
* @return SQL
*/
public String deleteById(ProviderContext context, Long id) throws Exception {
this.init(context);
if (id == null) {
throw new RuntimeException("id不能为null");
}
// 组装SQL
SQL sql = new SQL();
sql.UPDATE(this.tableName);
sql.SET(SqlConsts.EX_SQL_UNEFFECTIVE_DATA);
sql.WHERE(SqlConsts.PRIMARY_KEY_NAME + "=#{" + SqlConsts.PRIMARY_KEY_NAME + "}");
log.info("SQL -> " + sql.toString());
log.info("ID -> " + id);
return sql.toString();
}
/**
* 通过ID更新
*
* @param context 上下文
* @param entity <T extends BaseEntity>
* @return SQL
*/
public String updateSelectiveById(ProviderContext context, T entity) throws Exception {
this.init(context);
log.info(JSON.toJSONString(context));
// 如果ID为空
if (ReflectUtil.invoke(entity, SqlConsts.GET_ID) == null) {
throw new RuntimeException("id不能为null");
}
// 设置更新时间
ReflectUtil.invoke(entity, SqlConsts.SET_UPDATE_TIME, DateTime.now());
// 组装SQL
SQL sql = new SQL();
sql.UPDATE(this.tableName);
List<String> nonNullList = getNonNullList(entity, beanNames, tableGetNameMap, beanTableMap);
for (String beanName : nonNullList) {
// 不更新id字段
if (SqlConsts.PRIMARY_KEY_NAME.equalsIgnoreCase(beanName)) {
continue;
}
// 配置映射关系
sql.SET(this.beanTableMap.get(beanName) + "=#{" + beanName + "}");
}
sql.WHERE(SqlConsts.PRIMARY_KEY_NAME + "=#{" + SqlConsts.PRIMARY_KEY_NAME + "}");
sql.AND();
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
log.info("SQL -> " + sql.toString());
log.info("Entity -> " + JSON.toJSONString(entity));
return sql.toString();
}
/**
* 通过ID查询
*
* @param context 上下文
* @param id 主键
* @return SQL
*/
public String selectOneById(ProviderContext context, Long id) throws Exception {
this.init(context);
if (id == null) {
throw new RuntimeException("id不能为null");
}
// 组装SQL
SQL sql = new SQL();
sql.SELECT(this.baseColumnList);
sql.FROM(this.tableName);
sql.WHERE(SqlConsts.PRIMARY_KEY_NAME + "=" + id);
sql.AND();
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
log.info("SQL -> " + sql.toString());
log.info("ID -> " + id);
return sql.toString();
}
/**
* 通过类属性和值查询,并返回实体对象集合
*
* @param context 上下文
* @param nav 类属性名称和值的容器
* @return SQL
*/
public String selectByNav(ProviderContext context, @Param("nav") EntityPropNav nav) throws Exception {
this.init(context);
if (nav == null) {
throw new RuntimeException("nav不能为null");
}
if (nav.getName() == null) {
throw new RuntimeException("nav.name不能为null");
}
// 组装SQL
SQL sql = new SQL();
sql.SELECT(this.baseColumnList);
sql.FROM(this.tableName);
sql.WHERE(beanTableMap.get(String.valueOf(nav.name)) + "=#{nav.value}");
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
sql.ORDER_BY(SqlConsts.PR_SQL_ORDER_ID);
log.info("SQL -> " + sql.toString());
log.info("Nav -> " + JSON.toJSONString(nav));
return sql.toString();
}
/**
* 通过类属性和值模糊查询,并返回实体对象集合
*
* @param context 上下文
* @param nav 类属性名称和值的容器
* @return SQL
*/
public String selectByLikeNav(ProviderContext context, @Param("nav") EntityPropNav nav) throws Exception {
this.init(context);
if (nav == null) {
throw new RuntimeException("nav不能为null");
}
if (nav.getName() == null) {
throw new RuntimeException("nav.name不能为null");
}
if (nav.getValue() == null) {
throw new RuntimeException("nav.value不能为null");
}
// 组装SQL
SQL sql = new SQL();
sql.SELECT(this.baseColumnList);
sql.FROM(this.tableName);
sql.WHERE(beanTableMap.get(String.valueOf(nav.name)) + " like CONCAT('%',#{nav.value},'%')");
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
sql.ORDER_BY(SqlConsts.PR_SQL_ORDER_ID);
log.info("SQL -> " + sql.toString());
log.info("Nav -> " + JSON.toJSONString(nav));
return sql.toString();
}
/**
* 分页获取对象, 并返回实体对象集合
*
* @param context 上下文
* @param entity 查询对象
* @param page 分页对象
* @return SQL
*/
public String selectByPage(ProviderContext context, @Param("entity") T entity, @Param("page") Page page)
throws Exception {
this.init(context);
if (page == null) {
throw new RuntimeException(SqlConsts.TX_PAGE_NULL);
}
// 组装SQL
SQL sql = new SQL();
sql.SELECT(this.baseColumnList);
sql.FROM(this.tableName);
List<String> nonNullList = getNonNullList(entity, beanNames, tableGetNameMap, beanTableMap);
for (String beanName : nonNullList) {
sql.WHERE(this.beanTableMap.get(beanName) + "=#{entity." + beanName + "}");
}
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
int[] startEndIndex = PageUtil.transToStartEnd(page.getPage(), page.getPageSize());
sql.ORDER_BY(SqlConsts.PR_SQL_ORDER_ID + StrUtil.format(SqlConsts.PR_SQL_PAGE, startEndIndex[0], startEndIndex[1]));
log.info("SQL -> " + sql.toString());
log.info("entity -> " + JSON.toJSONString(entity));
log.info("Page -> " + JSON.toJSONString(page));
return sql.toString();
}
/**
* 不分页, 查询所有(存在全表查询风险,不建议大表与超大表使用)
*
* @param context 上下文
* @param entity 非空, 查询对象entity
* @return SQL
*/
public String selectAll(ProviderContext context, @Param("entity") T entity) throws Exception {
this.init(context);
// 组装SQL
SQL sql = new SQL();
sql.SELECT(this.baseColumnList);
sql.FROM(this.tableName);
List<String> nonNullList = getNonNullList(entity, beanNames, tableGetNameMap, beanTableMap);
for (String beanName : nonNullList) {
sql.WHERE(this.beanTableMap.get(beanName) + "=#{entity." + beanName + "}");
}
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
sql.ORDER_BY(SqlConsts.PR_SQL_ORDER_ID);
log.info("SQL -> " + sql.toString());
log.info("entity -> " + JSON.toJSONString(entity));
return sql.toString();
}
/**
* 不分页获取范围内对象, 并返回实体对象集合(这个估计会有点bug,如果查询字符串得加单引号不然mybatis会以为它是数据库字段名称)
*
* @param context 上下文
* @param propName 对象属性名称
* @param propValues 对象属性值集合
* @return SQL
*/
public String selectAllInValues(ProviderContext context, @Param("propName") String propName,
@Param("propValues") List<Object> propValues) throws Exception {
this.init(context);
if (propName == null) {
throw new RuntimeException("propName不能为null");
}
if (CollectionUtil.isEmpty(propValues)) {
throw new RuntimeException("propValues不能为null、或存在为null的对象");
}
// 组合ID
StringBuilder idsArrays = new StringBuilder();
for (Object propValue : propValues) {
idsArrays.append(propValue);
idsArrays.append(",");
}
SQL sql = new SQL();
sql.SELECT(this.baseColumnList);
sql.FROM(this.tableName);
sql.WHERE(beanTableMap.get(propName) + " in (" + idsArrays.substring(0, idsArrays.length() - 1) + ")");
sql.AND();
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
log.info("SQL -> " + sql.toString());
log.info("propName -> " + propName);
log.info("propValues -> " + JSON.toJSONString(propValues));
return sql.toString();
}
/**
* 条件统计数量
*
* @param context 上下文
* @param entity 非空, 查询对象entity
* @return SQL
*/
public String countAll(ProviderContext context, @Param("entity") T entity) throws Exception {
this.init(context);
SQL sql = new SQL();
sql.SELECT(SqlConsts.EX_SQL_CUSTOM_COUNT);
sql.FROM(this.tableName);
List<String> nonNullList = getNonNullList(entity, beanNames, tableGetNameMap, beanTableMap);
for (String beanName : nonNullList) {
sql.WHERE(this.beanTableMap.get(beanName) + "=#{entity." + beanName + "}");
}
sql.WHERE(SqlConsts.EX_SQL_EFFECTIVE_DATA);
log.info("SQL -> " + sql.toString());
log.info("entity -> " + JSON.toJSONString(entity));
return sql.toString();
}
/**
* 获取非空属性合集
*
* @param assetObj 被校验对象
* @param assetObjProps 被校验对象所有属性名称
* @param tableGetNameMap 表列名与对象GET属性方法名称的映射关系
* @param beanTableMap 对象属性名称与表列名的映射关系
* @return List<String>
*/
private List<String> getNonNullList(Object assetObj, List<String> assetObjProps, Map<String, String> tableGetNameMap,
Map<String, String> beanTableMap) {
List<String> result = new ArrayList<>(GlobalConsts.DEFAULT_COLLECTIONS_SIZE);
//assetObj不为空,即有查询条件的分页
if (assetObj != null) {
// 类属性值
Object beanPropValue;
// 遍历类属性
for (String assetObjProp : assetObjProps) {
// 1、通过beanName获取表字段名称
// 2、通过表字段名称获取get方法名称
// 3、通过反射获取属性值
beanPropValue = ReflectUtil.invoke(assetObj, tableGetNameMap.get(beanTableMap.get(assetObjProp)));
// 如果属性值不为null
if (beanPropValue != null) {
result.add(assetObjProp);
}
}
}
return result;
}
}
@@ -0,0 +1,50 @@
package cn.odboy.zilean.mybatis.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO BaseEntity 与数据库表结构一一对应,通过DAO层向上传输数据源对象(即Entity)
* @time: 2019/8/8 10:42
* @blog: www.odboy.cn
*/
@Getter
@Setter
@ToString
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
/**
* 自增主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(columnDefinition = "BIGINT(20) UNIQUE NOT NULL COMMENT '自增主键'")
protected Long id;
/**
* 创建时间
*/
@Column(columnDefinition = "DATETIME NOT NULL COMMENT '创建时间'")
protected Date createTime;
/**
* 更新时间
*/
@Column(columnDefinition = "DATETIME COMMENT '更新时间'")
protected Date updateTime;
/**
* 软删除标识(1、已删除 0、未删除)
*/
@Column(columnDefinition = "TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标识'")
protected Integer deleteFlag;
/**
* 备注信息
*/
@Column(columnDefinition = "VARCHAR(100) NOT NULL DEFAULT '' COMMENT '备注信息'")
protected String remark;
}
@@ -0,0 +1,22 @@
package cn.odboy.zilean.mybatis.page;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO 页面索引、数据数量
* @time: 2019/8/8 10:43
* @blog: www.odboy.cn
*/
@Getter
@Setter
@ToString
public class Page implements Serializable {
private Integer page = 1;
private Integer pageSize = 10;
}
@@ -0,0 +1,33 @@
package cn.odboy.zilean.mybatis.page;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO 分页对象
* @time: 2019/8/8 10:43
* @blog: www.odboy.cn
*/
@Getter
@Setter
@ToString
public class Pagination<T> implements Serializable {
/**
* 数据集合
*/
private List<T> rows;
/**
* 数据总数
*/
private Long total;
/**
* 页面参数
*/
private Page page;
}
@@ -0,0 +1,118 @@
package cn.odboy.zilean.mybatis.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
/**
* @author: Odboy
* @version: Jdk 1.8
* @desc: TODO 实体类属性名与数据库字段名互转
* @time: 2019/8/8 10:43
* @blog: www.odboy.cn
*/
public final class BeanColumnUtils {
private BeanColumnUtils() {
}
/**
* 类属性名称转SQL字段名
*
* @param beanPropName 类属性名称
* @return
*/
public static String getDataBaseColumnName(String beanPropName) {
if (!StringUtils.isEmpty(beanPropName)) {
StringBuilder sb = new StringBuilder();
for (char c : beanPropName.toCharArray()) {
if (Character.isUpperCase(c)) {
sb.append("_").append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
throw new NullPointerException("beanPropName不能为null");
}
/**
* 返回有的值条件,比如有个User类,有属性name,id
* 当只有id有值时,返回and id ={id的值}
*
* @return
* @throws
* @author ld
* @date 2019/02/11 14:13
*/
public String gainConditionFromObjectByField(Object object) {
StringBuilder sb = new StringBuilder();
// 取出所有属性
Field[] fields = object.getClass().getDeclaredFields();
for (Field f : fields) {
String fieldName = f.getName();
// 取属性值
Object value = getFieldValue(this, fieldName);
if (ObjectUtil.isNotNull(value)) {
Class<?> type = f.getType();
if ("class java.lang.String".equals(type.toString())) {
sb.append(" and ").append(fieldName).append("='").append(value).append("'");
} else {
sb.append(" and ").append(fieldName).append("=").append(value);
}
}
continue;
}
return sb.toString();
}
/**
* 获取字段值
*
* @param object 对象
* @param fieldName 类的属性名称
* @return 字段值
*/
private String getFieldValue(Object object, String fieldName) {
Object o = invokeMethod(object, fieldName);
return ObjectUtil.isNotNull(o) ? o.toString() : null;
}
/**
* 执行某个Field的getField方法
*
* @param object 对象
* @param fieldName 类的属性名称
* @return Object
*/
private Object invokeMethod(Object object, String fieldName) {
Class<?> testClass = object.getClass();
// 首字母大写
// fieldName -> FieldName
String methodName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
try {
return ReflectUtil.invoke(testClass, "get" + methodName);
} catch (Exception e) {
return null;
}
}
/**
* 首字母转大写
*
* @param s String
* @return String
*/
public static String toUpperCaseFirstOne(String s) {
if (Character.isUpperCase(s.charAt(0))) {
// 如果第一位为大写字母, 直接返回
return s;
} else {
return String.valueOf(Character.toUpperCase(s.charAt(0))) + s.substring(1);
}
}
}