brief 可以看作是一个依附于myabtis之上并提供mybatis所不具备的一些特性 (完全支持原生Mybatis). 目的是快速开发. 简化代码. 增强代码易读性. 提高项目可维护性. 比如 不用写xml文件, 不用写原生的字符sql,直接在接口的defalut方法中编写jql即可(java stream sql). 这种方式即加快了sql的编写又起到了规范作用. 常用的crud操作已经集成 不需要二次开发. 一对一.一对多,多对多映射关系不需要配置. 为了提高项目的可维护性 , 设计 jql api时避免了复杂的结构语句. 不会使项目中出现复杂的sql.可将复杂的sql拆解为多个jql (项目中杜绝复杂sql. 经历过的都明白). 为了提高可读性,jql的 形式接近原生sql. 为了提高编写效率,jql采用java stream的编写风格. 还有丰富的类型自动转换器. 比如表中的字段为long,我们 可以在实体类中直接定义Date/LoacalDateTime (具体可查看支持的类型转换.). 更多特性这里不一一介绍了. 接下来让我们进入brief
@BaseModel 表示是一个Model类,brief会自动解析映射表字段. brief并不会向mysql发送ddl语句(这是非常危险的). brief会根据@BaseModel解析出表名称(将Model类名解析为下划线格式作为数据库表名,如果表名和Model类型不一样则可以使用 @BaseModel的value属性进行指定),然后查询此表的字段信息并与Model类中的属性相关联. 关联的策略是将属性名称(驼峰形式) 转化成下划线并与表字段进行匹配,匹配成功才会进行关联并缓存这种关联关系. 所以Model类可以出现非表字段属性.提高了Model类的灵活 性. 非表字段属性不会被解析成sql. @BaseUnique 表示此表中具有唯一属性的字段(通常用于主键id)。 案例如下:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`birthday` datetime DEFAULT NULL
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
@BaseModel
@Data
public class User {
@BaseUnique
private Integer id;
private String name;
private Date birthday;
}
brief 以简单快速开发为理念,因此brief几乎没有学习成本. 我们只需要将我们的接口继承 CrudMapper 接口即可. T 为泛型Model类 (标记@BaseModel注解的类)。这样我们的Mapper接口在mybatis的基础上又拥有了jql的能力了. 在CrudMapper接口中主要有5个方法:select(), insert(), update(), delete(), general(). 就是这这么简单. 案例如下
public interface CrudUserMapper extends CrudMapper<User> {
//查询全表数据
default List<User> queryAll(){
return this.select()
.colAll()
.where()
.exs();
}
//增加user
default int addUser(User user){
Id ex = this.insert().colAll(user).ex();
return ex.toInt();
}
//.... 这里不一一举例了,
}
当然上面两个常用的方法其实你是不需要写的,因为在常用api中都已经实现了. 我们通过general()来调用常用api.
@Service
public class UserServiceImpl {
@Resource
CrudUserMapper crudUserMapper;
public List<User> queryAll(int pageNum, int size){
return crudUserMapper.general().query(pageNum, size);
}
}
我们的数据库主要就是查询数据,新增数据,更新数据以及删除数据. 因此设计了4个核心方法以及1个常用的api方法。
- select()
查询数据的入口方法
public interface CrudUserMapper extends CrudMapper<User>{
default List<User> queryUserByName(String name){
List<User> exs = this .select()
.col(User::getName)
.col(User::getSex)
.where()
.eq(User::getName, name)
.exs();
}
}
- insert()
新增数据的入口方法
public interface CrudUserMapper extends CrudMapper<User>{
default Integer saveUsers(User user){
Id id = this.insert()
.col(User::getName, user.getName())
.col(User::getSex, user.getName())
.where()
.ex();
return id.toInt();
}
}
- update()
更新数据的入口方法
public interface CrudUserMapper extends CrudMapper<User>{
default Integer updateUserNameById(User user){
Id id = this.update()
.npdateNull()
.col(User::getName, user.getName())
.where()
.eq(User::getId, user.getId())
.ex();
return id.toInt();
}
default Integer updateUserNameById2(User user){
Id id = this.update()
.updateNull()
.col(User::getName, user.getName())
.where()
.eq(User::getId, user.getId())
.ex();
return id.toInt();
}
default Integer updateUserById(User user){
Id id = this.update()
.updateNull()
.colAll(user)
.where()
.eq(User::getId, user.getId())
.ex();
return id.toInt();
}
default Integer updateUserById2(User user){
Id id = this.update()
.npdateNull()
.colAll(user)
.where()
.eq(User::getId, user.getId())
.ex();
return id.toInt();
}
}
-
col()指定具体要更新的字段
-
colAll() 更新全部字段(映射匹配的字段)
-
updateNull() 会包含 colname = null. 会将字段更为为空.
-
npdateNull() 过滤掉 colname = null. 不会将字段更新为空
-
delete()
删除数据的入口方法
public interface CrudUserMapper extends CrudMapper<User>{
default Integer deleteUserById(User user){
Id id = this.delete()
.where()
.eq(User::getId, user.getId())
.ex();
return id.toInt();
}
}
- general()
集成常用crud方法
import javax.annotation.Resource;
public class UserServiceImpl {
@Resource
CrudUserMapper crudUserMapper;
public User queryUserById(User user){
User user = crudUserMapper.general().queryById(save);
return user;
}
public Integer saveUser(User user){
int id = crudUserMapper.general().save(general).toInt();
return id;
}
public int updateUserById(User user){
return crudUserMapper.general().modifyById(user);
}
public void deleteUserById(int id){
crudUserMapper.general().removeById(id);
}
}
jql中有一基础Id类, 此类表示主键id. 当新增一条数据时,会返回Id对象. 表主键的类型可能不同。有varchar 或者 int 等. 因此 Id类包装了主键id的数据。我们可以通过toInt, toLong等方法获取自己想要的主键数据,当然如果你想获取原始数据可以通过 value() 方法获取。并且Id类会被jql底层进行解析当Id作为参数时。 比如:
// name = hello
User save = new User("hello")
//保存返回Id主键
Id id = crudUserMapper.general().save(save);
//Id主键返回int 类型
int idInt = id.toInt();
//下面这两句是等价的。
User user = crudUserMapper.general().queryById(id);
User user2 = crudUserMapper.general().queryById(idInt);
- col()
col()方法用于指定具体的字段。
- colAll()
colAll()方法在select()的时候查询所有字段,包括表字段和函数sql语句(指标有函数注解的)。 在insert() update() 时插入表所有字段.
ex() 和 exs() 表示使jql触发执行。不同的是ex()返回一条Model数据,而exs() 的执行结果则为集合。返回多条List 数据.
在jql中where()代表原生sql中的where. 在jql的设计中,select(), update(), delete() 都需要通过where()方法来触发ex()/exs()执行。 这样设计的目的是避免全表的失误操作 (总是提醒你不要忘记添加where条件)。当然如果你就是想进行全表操作也是可以的。比如where().ex() 或 者 where().exs(); where() 会被解析为 where 1=1 进行执行。
- eq()
- 表示等于
- unite()
- 表示括号逻辑,(xxxxx)
- condSQL
- 允许你写原生字符sql.
- or()
- 默认是and,通过or() 方法指定为or.
- groupBy
- 分组
- having
- 处理分组后的统计逻辑
字段主要分为表字段和sql函数语句。 表字段通过Model类的属性自动和表字段进行映射(属性转换为下划线格式后与表字段进行映射)。 如果表字段和Model类中的属性名称(属性转换为下划线格式后)不相同时。可以通过@ColName进行指定具体的表字段。
- 案例
@BaseModel
@Data
public class User {
@BaseUnique
private Integer id;
private String name;
private Date birthday;
//通过@ColName 指定 birthday 字段与 birthdayStr属性进行关联。
@ColName("birthday")
private String birthdayStr;
}
public interface CrudUserMapper{
default User queryUserBirthdayById(int id){
// SQL: select user.birthday as birthdayStr from user where 1=1 and user.id = #{id}
return this.select().col(User::getBirthdayStr).where().eq(User::getId, id).ex();
}
}
-
enum枚举支持
当Model类的属性是枚举时 ,jql对枚举类支持的也非常好. 如果枚举类中存在属性时我们可以通过@EnumValue来指定枚举类中的属性(具有唯一特性)。 该属性的值作为表字段属性的值。 如果没有@EnumValue则默认将枚举类的 ordinal() 的值作为表字段的默认值。 注意@EnumValue在枚举类中 只能指定一个属性.不能指定多个
@BaseModel @Data public class User { @BaseUnique private Integer id; private String name; private Sex sex; private Work work; } //没有属性可以不使用@EnumValue public enum Sex { Girl, Boy } //有属性需要用@NnumValue进行指定. public enum Work { JAVA(1,"JAVA"), PYTHON(2,"PYTHON") ; @EnumValue private int code; private String workName; Work(int code, String workName){ this.code = code; this.workName = workName; } }
-
@ColName
@ColName 非常灵活,不仅可以指定表字段名称。还可以指定sql语句。本质上是将@ColName的 值最后将会被解析到要执行的sql语句中。@ColName 与 Sql函数注解也经常配合使用(不是必须的)。 @ColName的excludeColAll属性默认,是否参与select().colAll()查询。(默认参与查询)
注解类名称与mysql数据库中的函数名称保持一致。多个sql注解可以组合使用。 多个组合sql函数注解最后会解析为嵌套的sql函数逻辑。组合注解解析的顺序 是从上向下开始解析。
- 有参函数
- 通用函数
- @if
- @ifEq
- xxxx
- 字符函数
- @CharLength
- Concat
- xxxx
- 时间函数
- DateFormat
- Dayname
- xxxx
- 数学函数
- @Abs
- @Ceil
- 通用函数
- 无参函数 不需要和@ColName进行配合使用
- 时间函数
- @Curdate
- CurrentDate
- xxxx
- 数学函数
- @Rand
- 时间函数
@BaseModel
@Data
public class User {
@BaseUnique
private Integer id;
@Trim()
private String name; //trim(name)
private Sex sex;
@ColName("name")
@Trim()
@Concat("sex")
private String concatName; // concat(trim(name),sex)
}
该注解属于分组sql函数注解,当执行select().colAll() 时可能会将该注解对应的sql语句不进行解析. 因为colAll()通常是查询所有字段, 除非你按照所有字段进行了分组。这种场景不是很多。如果你想让colAll() 包含@GroupConcat进行解析,则只需要将@GroupConcat中的 excludeColAll 属性改为false即可,默认为true. 另外一种方式是通过col(xxx) 来指定也是可以的(推荐这种方式)
我们通过注解的方式使Model类的字段数据进行脱敏. 脱敏的字段必须声名为String类型. 提供了常用的注解和通用的注解
- @PhoneNumBlur 手机号脱敏
- @EmailBlur 邮件脱敏. @部分不会进行脱敏
- @IdCardBlur 身份证号脱敏.
- @StringBlur 字符串脱敏. 上面的注解也可以用此注解进行替换.
@StringBlur 可以用于任何String类型的属性. 不同点是在进行脱敏时不会进行保留特征处理. 比如@EmailBlur 会保留@后部分不进行脱敏处理. 但是@StringBlur则统一按照字符进行脱敏.
我们可以指定脱敏的开始位置, 从左边或者右边开始脱敏. 默热时中间开始脱敏. 也可以指定脱敏的范围比(0-1), 还可以指定脱敏的符号. 默认是 '*'
- percent: 脱敏的范围, 用百分比表示. (0-1)
- blur : 脱敏开始位置. (默认中间)
- blurTag: 脱敏符号. (默认 '*')
jql 支持多表映射,而且非常简单没有复杂的配置。 一对一, 一对多,多对多。用Model类表示 则为类与类(一对一),类与集合的关系(一对多,多对多)。 jql 设计最多3张表进行join. 如果你想多表(3张以上)可以使用直接写原生myabtis. jql这样设计的原因是为了提高项目的可维 护性和简化sql. (本人经历过超过10张表的join. 后期这个join基本上就没有维护的空间. 而且 随着数据量的上涨sql的执行效率越来越底. 更可怕的是这个大join关联的业务比较多. 最后只能重 写和这个大join关联的所有业务. 大join会将维护成本提高n倍. 所以我们还是尽量避免) 来我们 看一下案例.
- 一对一 (类与类的关系)
@BaseModel
@Data
public class User {
@BaseUnique
private Integer id;
private String name;
private Card card; //一对一。 不需要任何配置就是这么简单
}
//身份证号
@BaseModel
@Data
public class Card{
//主键id
@BaseUnique
private Integer id;
//身份证号
private String cardNum;
//用户id
private Interge userId;
}
- 一对多 (类与集合的关系)
@BaseModel
@Data
public class User {
@BaseUnique
private Integer id;
private String name;
private List<Order> order; //一对多。 不需要任何配置就是这么简单
}
//订单
@BaseModel
@Data
public class Order{
//主键id
@BaseUnique
private Integer id;
//订单号
private String orderNum;
//用户id
private Interge userId;
}
- 多对多 (类与集合的关系)
@BaseModel
@Data
public class Student {
@BaseUnique
private Integer id;
private String name;
private List<Teacher> teachers; //多对多。 不需要任何配置就是这么简单
}
//老师
@BaseModel
@Data
public class Teacher{
//主键id
@BaseUnique
private Integer id;
//老师名称
private String teacherName;
//学科
private String subject;
}
//中间表
@BaseModel
@Data
public class StudentTeacher{
//主键id
@BaseUnique
private Integer id;
//学生id
private Integer studentId;
//老师id
private Integer teacherId;
}
- left join
List<User> users = crudUserMapper
.select()
.colAll()
.leftJoin(UserOrder::new)
.colAll()
.on()
.oeq(User::getId, UserOrder::getUserId)
.where()
.exs();
- right join
List<User> users = crudUserMapper
.select()
.colAll() //查询user的所有字段和sql函数语句(标有函数注解的也将会被解析)
.rightJoin(UserOrder::new)
.colAll()
.on()
.oeq(User::getId, UserOrder::getUserId)//绑定两张表的关系字段。
.where()
.exs();
- inner join (多对多案例)
List<User> users = crudUserMapper
.select()
.colAll()//查询user所有字段
.innerJoin(UserTeacher::new) //关联中间表
.on()
.oeq(User::getId, UserTeacher::getUserId) //指定关联关系
.innerJoin(Teacher::new)//关联子表
.colAll()//查询子表所有字段
.on()
.oeq(UserTeacher::getTeacherId, Teacher::getId)//指定关联关系
.where()
.exs();//执行
在join 进行关联时,我们可以使用on来指定两张表的关系。 以oxx开头的方法是用来描述两张表之间的关系。 如果你想指定子表的某一个字段的逻辑关系,可是调用非o开都的方法即可。非常简单。
jql中提供了很多常用的api操作,不需要开发人员二次开发。我们可以通过general()方法来使用常用方法。 比如:User user = crudUserMapper.general().queryById(id); 这里我们来举例部分. 您也可以通 过 general() 来查看全部支持的方法。
- 常用api
- save() 保存
- saveOrModify() 保存或更新,解析的sql为 insert into xxx on duplicate key update xxx
- saveOrReplace() 对应sql中的replace into
- saveBatch() 批量保存。进行批处理。效率非常高。
- remove() 会将model对象属性不为空解析成where条件进行执行delete
- removeById() 根据id删除
- modifyById() 根据id执行更新操
- modifyBatchById() 支持批量更新,底层会进行优化当更新的字段相同时会进行分组,每组作为一个批次进行更新
- query() 会将model对象属性不为空解析成where条件进行执行 select. 还有各种其他的query()这里只举例部分
- count() 统计表数量
为了统一jql的编码风格。并且方便项目的维护。防止sql/jql出现的到处都是。因此在设计jql的时候支持了在接口中编写defalue 风格。在接口中的default方法中直接可以编写jql.
在实际的开发中,我们通常把数据查看出来然后再进行格式化转换。为了避免这种问题没,我们支持了自动类型转换。常用的类型 转换器我们已经进行了实现。开发者只需要在Model类中定义自己想要的类型即可。 我们当前支持的类型转换如下。 比如 Number2DateConvert 表示 number数字转换为date日期 .
- String2DoubleConvert
- DateOne2DateTwoConvert
- String2DateConvert
- Boolean2StringConvert
- Date2OffsetDateTimeConvert
- Date2LongConvert
- Number2SQLDateConvert
- String2ByteConvert
- ByteArray2StringConvert2
- Number2DateConvert
- Date2LocalDateTimeConvert
- String2LocalDateConvert
- String2OffsetDateTimeConvert
- Number2StringConvert
- String2FloatConvert
- Date2StringConvert
- String2BooleanConvert
- String2ShortConvert
- PrimitiveNumber2PrimitiveNumberConvert
- String2LongConvert
- LocalDate2StringConvert
- String2CharConvert
- Character2StringConvert
- String2IntegerConvert
- Number2LocalDateConvert
- Number2PrimitiveConvert
- String2LocalDateTimeConvert
- Date2LocalDateConvert
- String2SQLDateConvert
- ByteArray2StringConvert
- String2BigDecimalConvert
- Number2BooleanConvert
- String2BigIntegerConvert
- Number2LocalDateTimeConvert
- Number2EnumConvert
- String2EnumConvert
当我们需要将数据库表中的某些字段进行加解密时。brief 提供了一个简单的配置就可以搞定。 我们只需要指定一个key(长度是32个十六进制). 然后指定表和表中的字段即可。加解密的 事情就交给框架去做.下面我们举例来说. 我们指定一个私钥是 ‘AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF’ 并且给表 encrypt_data 中的encrypt_num 加密. 配置如下:
/**
* Configure the tables and fields that need to be decrypted.
* the key Is the length of 32 hexadecimal;
*/
@AesEncryptConfig(key = "FFFFFFFFAAAAAAAAAAAAFFFFFAFAFAFA", encryptTableColumns = {
@EncryptTableColumns(tableName = "encrypt_data", columns = {"encrypt_num"})
})
@Configuration
static class EncryptConfig{ }
EncryptData encryptData = new EncryptData();
String encryptNum = "1234567890";
encryptData.setEncryptNum(encryptNum);
//encrypt_num的值在表里是: 396195EAF65E740AEC39E6FFF0714542
Id id = this.crudEncryptDataMapper.general().save(encryptData);
//查询的结果被自动解密
encryptDatas = this.crudEncryptDataMapper.general().queryByIds(id);
print(encryptDatas); //[{"id":10,"encryptNum":"1234567890"}]
//查询时可以指定铭文去查询,jql会转化成密文去匹配. 因此可以正确的查询到数据。
EncryptData ex = this.crudEncryptDataMapper.select().colAll().where().eq(EncryptData::getEncryptNum, encryptNum).ex();
print(ex);//{"id":10,"encryptNum":"1234567890"}