# dream
**Repository Path**: dongdongxiaozi54/dream
## Basic Information
- **Project Name**: dream
- **Description**: 基于翻译的持久层框架
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 11
- **Created**: 2023-07-26
- **Last Updated**: 2023-07-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 快速入门
## **简介**
DREAM( https://github.com/moxa-lzf/dream ) 是一个基于翻译的以技术为中心,辐射业务持久层框架,它非常轻量,不依赖第三方jar包、同时拥有极高的性能与灵活性,可以写一种MySQL语法在非MySQL数据库下执行,其内置的QueryDef不仅帮助开发者极大减少SQL编写的工作同时,减少出错的可能性,而且基本上支持MySQL所有函数,支持常见的SQL语句改写成这种形式。
总而言之,DREAM不仅能够极大的提高开发效率与开发体验,让开发者有更多的时间专注于自己的事,而且还能根据业务进行函数化封装。
## **特性**
**跨平台**:支持mysql语法在非mysql环境下执行,并提供接口自定义翻译
**轻量级**:整个框架不依赖任何第三方依赖,而且做到了对每一条SQL深度解析,可改写SQL,性能更优
**灵活**:匠心独运的架构设计,结构层次分明,特别注重如何优雅的设计,设计强调用户开发应该继承接口实现自定义,而不是传入参数
**函数化**:设计围绕的核心,原理基于SQL的深度解析,开发者可自定义开发与业务有关的高级功能,愿景:每个公司都有自己的函数库
**强大**:注解校验,数据权限,逻辑删除,多租户,多数据源,参数值注入与修改(可完成注入默认值、字段加密等),主键序列(可自定义)、查询字段值提取(可完成解密、查询字典和库表、脱密等)
## 支持的数据库
DREAM支持MySQL、PGSQL、SQLSERVER、ORACLE、达梦,其他数据库语法和提供支持的数据库语法类似,对于特殊的数据库,开发者也可以自己写对应的SQL转换语句,把抽象树转换对应可执行的SQL即可
## **系统架构**
## **基础开发**
### 模板操作
#### 基础操作
dream提供实例的TemplateMapper完成基础操作
| 方法名 | 描述 |
|--------------------------------------------------------------|-------------------------|
| selectById(Class type, Object id) | 主键查询(支持多表关联查询) |
| selectByIds(Class type, Collection> idList) | 主键批量查询(支持多表关联查询) |
| selectOne(Class type, Object conditionObject) | 根据注解生成条件,查询一条 |
| selectList(Class type, Object conditionObject) | 根据注解生成条件,查询多条 |
| selectTree(Class type, Object conditionObject) | 根据注解生成条件,查询,并返回树形结构 |
| selectPage(Class type, Object conditionObject, Page page) | 根据注解生成条件,分页查询多条 |
| updateById(Object view) | 主键更新 |
| updateNonById(Object view) | 主键非空更新,注意:空字符串也更新 |
| insert(Object view) | 插入 |
| insertFetchKey(Object view) | 插入并在view属性记录主键值 |
| deleteById(Class> type, Object id) | 主键删除 |
| deleteByIds(Class> type, Collection> idList) | 主键批量删除 |
| existById(Class> type, Object id) | 判断主键是否存在 |
| exist(Class> type, Object conditionObject) | 根据注解生成条件,判断是否存在 |
| batchInsert(Collection> viewList) | 批量插入,默认一千作为一个批次,可设置批次 |
| batchUpdateById(Collection> viewList) | 批量主键更新,默认一千作为一个批次,可设置批次 |
#### 注解操作
##### Validated
用法:对传入的参数进行校验
```java
public @interface Validated {
Class extends Validator> value();
}
```
| 属性名 | 描述 |
|-------|-----|
| value | 校验器 |
```java
public interface Validator {
default boolean isValid(Session session, Class type, Field field, Command command) {
return true;
}
void validate(T value, Map paramMap);
}
```
| 属性名 | 描述 |
|-------------------------------------------|--------------------------------|
| isValid | 返回值标识是否进行校验,如校验,才会进行validate方法 |
| validate,参数value:待校验的值,paramMap:开发者自定义的参数 | 数据校验,选择性抛异常 |
已实现的Validator
| Validator类 | 描述 |
|----------------------|----------------------------|
| AssertFalseValidator | 校验值若不为空,值必须为false |
| AssertTrueValidator | 校验值若不为空,值必须为true |
| LengthValidator | 校验值若不为空,校验值长度 |
| MaxValidator | 校验值若不为空,校验值是否超过最大值 |
| MinValidator | 校验值若不为空,校验值是否小于最小值 |
| NotBlankValidator | 校验值不能为空,且不能为空字符串 |
| NotNullValidator | 校验值不能为空 |
| PatternValidator | 校验值若不为空,校验满足正则表达式 |
| RangeValidator | 校验值若不为空,校验值是否在规定范围 |
| SizeValidator | 校验值若不为空,校验集合或map的大小是否在规定范围 |
| UniqueValidator | 校验值若不为空,与数据库校验值是否唯一 |
##### **Wrap**
用法:参数值注入与修改,可完成填充默认值,字段加密等操作
```java
public @interface Wrap {
Class extends Wrapper> value();
WrapType type() default WrapType.INSERT_UPDATE;
}
```
| 属性名 | 描述 |
|-------|------------------|
| value | 处理的实现类 |
| type | 处理时机,更新,插入,更新或插入 |
```java
public interface Wrapper {
Object wrap(Object value);
}
```
| 参数名 | 描述 |
|-------|----------------|
| value | 参数传入值,返回为处理后的值 |
##### **Conditional**
用法:指定生成的where条件
```java
public @interface Conditional {
String table() default "";
boolean nullFlag() default true;
boolean or() default false;
Class extends Condition> value();
}
```
| 属性名 | 描述 |
|----------|----------------|
| table | 条件的表名 |
| nullFlag | 为空是否剔除(空字符串为空) |
| or | 是否采用or,默认and |
| value | 生成条件的实现类 |
```java
public interface Condition {
String getCondition(String table, String column, String field);
}
```
| 参数名 | 描述 |
|--------|--------|
| table | 表名称 |
| column | 数据库字段名 |
| field | 对象属性名称 |
已实现的Condition
| Condition类 | 描述 |
|--------------------|-----------------|
| ContainsCondition | like '%?%' |
| EndWithCondition | like '?%' |
| EqCondition | =? |
| GeqCondition | > =? |
| GtCondition | > ? |
| InCondition | in(?,?) |
| NotInCondition | not in (?,?) |
| LeqCondition | <=? |
| LtCondition | |
| NeqCondition | <>? |
| NotNullCondition | is not null |
| NullCondition | is null |
| StartWithCondition | like '%?' |
| BetweenCondition | between ? and ? |
举例:对于mybatis语法
```xml
where 1=1
and user.name like('%',name,'%')
and age in
#{item}
```
可改写成
```java
public class UserCondition {
@Conditional(table = "user", value = ContainsCondition.class)
private String name;
@Conditional(value = InCondition.class)
private List age;
}
```
##### **Sort**
用法:排序
```java
public @interface Sort {
String table() default "";
Order value() default Order.ASC;
int order() default 0;
}
```
| 属性名 | 描述 |
|-------|-------------------------|
| table | 表名称 |
| value | 排序方式 |
| order | 指定多个排序字段时,显示优先级,越小优先级越高 |
### 流式操作
对与工作经验多年的人来说,写SQL语句如家常便饭,SQL语法才是最强大的,但对与一些新手来说,写SQL语句很容易出错,而且即便错啦,也不知道哪里出错,因此提供好的提示,并实时知道哪里出错是十分必要的。
硬解码SQL
```sql
select u2.id,u2.name from (select id,name from user) u2 left join blog on u2.id=blog.user_id
```
改写成流式
```java
UserTableDef u2 = new UserTableDef("u2");
select(u2.id, u2.name)
.from(table(select(user.id, user.name).from(user)).as("u2"))
.leftJoin(blog)
.on(user2.id.eq(blog.user_id))
```
这里仅仅展示了其中的一个方面,流式已经基本上全面支持SQL写法,而且写法规范基本上和SQL语法保持一致
### 自定义Mapper操作
归根到底手写SQL才是最强大的
```java
@Mapper(BlogMapperProvider.class)
public interface BlogMapper {
@Sql("select @*() from blog where user_id=@?(userId)")
List selectBlogByUserId(@Param("userId")Integer userId);
List selectBlogByUserId2(Integer userId);
default List selectBlogByUser(UserView userView) {
return selectBlogByUserId(userView.getId());
}
}
```
Param:指定参数名称
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
String value();
}
```
| 属性名 | 描述 |
|-------|------|
| value | 参数名称 |
每个接口方法必须绑定对应的SQL,有两种绑定形式:
1:在方法上声明注解Sql
```java
public @interface Sql {
String value();
boolean cache() default true;
int timeOut() default 0;
}
```
| 属性名 | 描述 |
|-------|------------|
| value | 绑定的SQL语句 |
| cache | 是否进行数据缓存读取 |
| time | 超时设置 |
2:在Mapper声明接口方法绑定的Sql
```java
public class BlogMapperProvider {
public String selectBlogByUserId2() {
return "select @*() from blog where user_id=@?(userId)";
}
}
```
也可以返回ActionProvider对象,进行SQL增强操作
```java
public class BlogMapperProvider {
public ActionProvider selectBlogByUserId2() {
return new BlogActionProvider();
}
}
```
```java
public interface ActionProvider {
String sql();
default Action[] initActionList() {
return null;
}
default Action[] destroyActionList() {
return null;
}
default Class extends Collection> rowType() {
return null;
}
default Class> colType() {
return null;
}
default Boolean cache() {
return null;
}
default Integer timeOut() {
return null;
}
default StatementHandler statementHandler() {
return null;
}
default ResultSetHandler resultSetHandler() {
return null;
}
}
```
| ActionProvider方法 | 描述 |
|-------------------|------------------|
| sql | 待执行的SQL语句 |
| initActionList | SQL执行前,待执行的行为 |
| destroyActionList | SQL执行后,待执行的行为 |
| rowType | 接受的集合类型,一般系统判断即可 |
| colType | 接受的对象类型,一般系统判断即可 |
| cache | 是否使用缓存 |
| timeOut | 超时设置 |
| statementHandler | 最终交互的数据库操作,默认即可 |
| resultSetHandler | 自定义结果集映射 |
# 用法教程
## **内置@函数**
### **?**
#### **用法**
与参数有关的函数,将参数改成?
#### **举例**
```java
@Mapper
public interface UserMapper {
@Sql("select id, name, age,email from user where name = @?(name)")
User findByName(String name);
}
```
#### **测试**
```java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class QueryTest {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
User user = userService.findByName("Jone");
}
}
```
#### **控制台输出**
```tex
SQL:SELECT id,name,age,email FROM user WHERE name=?
PARAM:[Jone]
```
### **rep**
#### **用法**
与参数有关的函数,将参数带入sql
#### **举例**
```java
@Mapper
public interface UserMapper {
@Sql("select id, name, age,email from user where name = @rep(name)")
User findByName2(String name);
}
```
#### **测试**
```java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class QueryTest {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
User user = userMapper.findByName2("'Jone'");
}
}
```
#### **控制台输出**
```tex
SQL:SELECT id,name,age,email FROM user WHERE name='Jone'
PARAM:[]
```
### **foreach**
#### **用法**
遍历集合或数组
#### **举例:删除数组**
```java
@Mapper
public interface UserMapper {
@Sql("delete from user where id in (@foreach(list))")
int delete(List idList);
}
```
#### **测试**
```java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class DeleteTest {
@Autowired
private UserMapper userMapper;
@Test
public void deleteById2() {
templateMapper.deleteByIds(User.class, Arrays.asList(1, 2, 3, 4, 5, 6));
}
}
```
#### **控制台输出**
```tex
SQL:DELETE FROM user WHERE id IN (?,?,?,?,?,?)
PARAM:[1, 2, 3, 4, 5, 6]
```
### **non**
#### **用法**
空条件剔除
#### **举例**
```java
@Mapper
public interface UserMapper {
@Sql("update user set @non(name=@?(user.name),age=@?(user.age),email=@?(user.email)) where id=@?(user.id)")
Integer updateNon(User user);
}
```
**注:空字符串不为空**
#### **测试**
```java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class UpdateTest {
@Autowired
private UserMapper userMapper;
@Test
public void updateNonId2() {
User user = new User();
user.setId(1);
user.setName("hli");
user.setEmail("");
userMapper.updateNon(user);
}
}
```
#### **控制台输出**
```tex
SQL:UPDATE user SET name=?,email=? WHERE id=?
PARAM:[hli, , 1]
```
### **not**
### **用法**
空条件剔除
注:空字符串为空
### *****
#### **用法**
1:根据java属性识别查询字段
2:根据SQL查询前后文排除字段
#### **举例**
```java
@Mapper
public interface UserMapper {
@Sql("select @*(),'hello' name from user")
List findAll();
}
```
**注:后文查询 'hello'**
#### **测试**
```java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class QueryTest {
@Autowired
private UserMapper userMapper;
@Test
public void test3() {
List userList = userMapper.findAll();
userList.forEach(System.out::println);
}
}
```
#### **控制台输出**
```tex
SQL:SELECT user.id,user.age,user.email,'hello' name FROM user
PARAM:[]
TIME:25ms
User{id=1, name='hello', age=18, email='test1@baomidou.com'}
User{id=2, name='hello', age=20, email='test2@baomidou.com'}
User{id=3, name='hello', age=28, email='test3@baomidou.com'}
User{id=4, name='hello', age=21, email='test4@baomidou.com'}
User{id=5, name='hello', age=24, email='test5@baomidou.com'}
```
### **table**
#### **用法**
自动将表拼接成关联条件
#### **举例**
```java
public interface UserMapper {
@Sql("select @*() from @table(user,blog)")
List selectAll3();
}
```
### **测试**
```java
@Test
public void test8(){
List userList=userMapper.selectAll3();
}
```
#### **控制台输出**
```tex
执行SQL:SELECT user.id,user.name,user.age,user.email,blog.id,blog.name,blog.user_id FROM user LEFT JOIN blog ON user.id=blog.user_id
执行参数:[]
执行用时:17ms
```
## **注解**
### **Table**
#### **用法**
绑定类对象与数据表
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
}
```
| 注解属性 | 描述 |
| -------- | ---------------- |
| value | 指定绑定的数据表 |
#### **举例**
```java
@Table("user")
public class User {
}
```
### **Id**
#### **用法**
声明表主键
**注:应用于仅当表有且仅有一个主键**
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
}
```
#### **举例**
```java
@Table("user")
public class User {
@Id
@Column("id")
private Integer id;
}
```
### **Column**
#### **用法**
绑定类对象属性与数据表字段
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value();
int jdbcType() default Types.NULL;
}
```
| 注解属性 | 描述 |
|----------|----------|
| value | 绑定的数据表字段 |
| jdbcType | 数据表字段类型 |
#### **举例**
```java
@Table("user")
public class User {
@Id
@Column("id")
private Integer id;
@Column(value = "name", jdbcType = Types.VARCHAR)
private String name;
@Column("age")
private Integer age;
@Column("email")
private String email;
}
```
### **Join**
#### **用法**
指明表于表关联关系,目的为消灭sql语句写表与表关联而生,@函数table基于此
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Join {
String column();
String joinColumn();
JoinType joinType() default JoinType.LEFT_JOIN;
}
```
| 注解属性 | 描述 |
|------------|---------|
| column | 该表字段名 |
| joinColumn | 关联表的字段名 |
| joinType | 关联类型 |
**注:数据表名根据修饰的类属性判断**
#### **举例**
```java
@Table("user")
public class User {
@Id
@Column("id")
private Integer id;
@Column(value = "name", jdbcType = Types.VARCHAR)
private String name;
@Column("age")
private Integer age;
@Column("email")
private String email;
@Join(column = "id", joinColumn = "user_id", joinType = Join.JoinType.LEFT_JOIN)
private List blogList;
}
```
**注:类Blog必须有注解Table**
### **View**
#### **用法**
视图概念,截取数据表的部分数据操作
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface View {
Class> value();
}
```
| 注解属性 | 描述 |
|-------|----------|
| value | 来源数据表映射类 |
**注:View修饰的类属性,必须和Table修饰的属性一致,才能做到映射**
#### **举例**
仅仅就想查询id以及name字段,email与age不查询
```java
@View(User.class)
public class UserView2 {
private Integer id;
private String name;
}
```
```java
@Sql("select @*() from user")
List selectAll2();
```
#### **测试**
```java
@Test
public void test7(){
List userViews=userMapper.selectAll2();
}
```
#### **控制台输出**
```java
SQL:SELECT user.id,user.name FROM user
PARAM:[]
TIME:33ms
```
**注:做到修改字段就可以间接修改SQL语句目的,存在情况,view字段与table字段一致,但不想查询,或者不想多表查询,可以使用Ignore忽略此字段
**
### **Ignore**
#### **用法**
查询结果自动映射到对象,忽视此字段
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Ignore {
}
```
#### **举例**
```java
@View("user")
public class UserView {
private Integer id;
private String name;
@Ignore
private String email;
}
```
**注:email存在Ignore,email数据为空**
### **PageQuery**
#### **用法**
分页
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PageQuery {
boolean offset() default false;
String value() default "page";
}
```
| 属性名 | 描述 |
|--------|----------------------|
| offset | 是否使用offset分页,默认limit |
| value | Page对象地址 |
#### **举例**
```java
@Mapper
public interface UserMapper {
@Sql("select id, name, age,email from user order by id")
@PageQuery("page")
List findByPage(@Param("page") Page page);
}
```
#### **测试**
```java
@Test
public void testPage(){
Page page=new Page(1,1);
List userList=userMapper.findByPage(page);
page.setRows(userList);
System.out.println("总数:"+page.getTotal());
}
```
#### **控制台输出**
```tex
SQL:SELECT id,name,age,email FROM user ORDER BY id LIMIT ?,?
PARAM:[0, 1]
SQL:SELECT COUNT(1) FROM user
PARAM:[]
TIME:24ms
TIME:36ms
总数:5
```
### **Extract**
用法:对查询的值做处理,列如,解密,字段脱敏,反查字典等操作
```java
public @interface Extract {
Class extends Extractor> value();
String[] args() default {};
}
```
| 属性名 | 描述 |
|-------|----------|
| value | 提取的具体操作类 |
| args | 自定义参数 |
```java
public interface Extractor {
default void setArgs(String[] args) {
}
void extract(String property, Object value, ObjectFactory objectFactory);
}
```
| 参数名 | 描述 |
|---------------|---------------|
| property | 属性名 |
| value | 数据库查询的值 |
| objectFactory | 反射工厂,用来给字段填充值 |
## **监听器**
### **用法**
检查、阻断SQL、SQL审计、修改查询数据
```java
public interface Listener {
boolean before(MappedStatement mappedStatement);
Object afterReturn(Object result, MappedStatement mappedStatement);
void(Exception e, MappedStatement mappedStatement);
}
```
| 方法名 | 描述 |
|-------------|--------------------|
| before | 返回false,SQL不执行,返回空 |
| afterReturn | 返回结果为查询结果 |
| exception | 出现异常调用此处 |
## **插件**
### **用法**
基于接口代理实现,可以修改参数
```java
public interface Interceptor {
Object interceptor(Invocation invocation) throws Throwable;
Set methods();
}
```
| 方法名 | 描述 |
|-------------|----------|
| interceptor | 此处进行注入插件 |
| methods | 拦截感兴趣的方法 |
# **高级开发**
### **关键字插件**
数据库关键字,不是关键字可以不加特殊符号,关键字必须要加,dream提供方案,SQL语句可以不加特殊符号对关键字处理,一样可以正常执行
SQL语句,若user和id为关键字,不做处理会执行报错,正确做法需要对user和id加特殊符号
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON b.user_id = u.id
) t_tmp LIMIT 1
```
开启关键字插件
```sql
SELECT *
FROM (
SELECT u.`id`,
u.NAME,
u.age,
u.email,
b.`id` bId,
b.NAME bName
FROM `USER` u
LEFT JOIN blog b ON b.user_id = u.`id`
) t_tmp LIMIT 1
```
自动完成对user和id关键字处理,性能等价于直接写关键字处理
#### **开启插件**
```java
@Bean
public Inject[] injects() {
return new Inject[]{new BlockInject("META-INF/keyword.txt")};
}
```
META-INF/keyword.txt记录了自定义关键字
### **多数据源**
#### **开启注解**
```java
public @interface EnableShare {
Class extends DataSource> value();
}
```
| 属性 | 描述 |
|-------|-----------------|
| value | DataSource实现类类型 |
#### **数据源配置**
```yaml
dream:
datasource:
master:
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.0.3/d-open
username: root
password: root
keepaliveTime: 1000
isReadOnly: false
slave:
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.0.3/d-open-6c
username: root
password: root
```
注:dream.datasource固定,master和slave为数据连接池名称,其他为数据连接池字段属性
#### **数据源选择**
```java
public @interface Share {
String value();
}
```
| 属性 | 描述 |
|-------|-------------------|
| value | 数据连接池名称,默认是master |
#### **举例**
```java
@Share("master")
public interface UserMapper {
@Sql("select id, name, age,email from user where name = @?(name)")
List findByName(String name);
@Share("slave")
@Sql("select id, name, age,email from user where name = @rep(name)")
List findByName2(String name);
}
```
### **多租户**
考虑同一个库,同一个schema情况,将现有项目改写成多租户,实现成本是多少,可能会说成本太大啦,所有SQL基本上都要翻新,而dream却给了你0成本方案,既然无感知,成本自然为0
查询用户表user和文章表blog的前一条数据
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON b.user_id = u.id
) t_tmp LIMIT 1
```
若用户表和文章表都存在租户字段,将其改造为多租户,dream可以让你不用修改当前SQL,在启动类添加开启多租户插件即可自动将其改造成多租户
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON (b.user_id = u.id)
AND b.tenant_id = ?
WHERE u.tenant_id = ?) t_tmp LIMIT 1
```
dream的识别是高强度的,不会因为SQL复杂,漏加任何租户条件,那性能如何?是等价于直接写租户条件的,无性能损耗
#### **开启多租户**
```java
@Bean
public Inject[] injects() {
return new Inject[]{new TenantInject(() -> 1)};
}
```
注:重写TenantHandler完成租户需求
```java
public interface TenantHandler {
default boolean isTenant(MethodInfo methodInfo, TableInfo tableInfo) {
return tableInfo.getFieldName(getTenantColumn()) != null;
}
default String getTenantColumn() {
return "tenant_id";
}
Object getTenantObject();
}
```
| 方法名 | 描述 |
|-----------------|----------------------------------------------------------|
| isTenant | 判断当前方法或当前表是否应用租户MethodInfo:记录了方法的一切信息TableInfo:记录了表的一切信息 |
| getTenantColumn | 租户字段 |
| getTenantObject | 租户值 |
**注:一旦当前方法应用租户,租户将完全由系统接管,插入对租户字段赋值,更新赋值将失效**
### **数据权限**
采用mybatis方案进行数据权限隔离,会在where条件注入 ${权限条件},是否可以不写${权限条件},一样完成数据权限注入,这样实现才是真正意义上的权限SQL与业务SQL解耦
同样SQL,需要注入数据权限,假如:查询自己所在部门
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON b.user_id = u.id
) t_tmp LIMIT 1
```
开启数据权限插件
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON b.user_id = u.id
WHERE u.dept_id = 1
) t_tmp LIMIT 1
```
u.dept_id=1是开发者自己注入的数据权限,不要担心,dream会解析出别名告诉开发者,完成数据权限注入,此时,SQL非常清爽,性能等价于在SQL直接写注入权限条件
#### **开启数据权限**
```java
@Bean
public Inject[] injects() {
return new Inject[]{new PermissionInject(new PermissionHandler() {
@Override
public boolean isPermissionInject(MethodInfo methodInfo, TableInfo tableInfo) {
return tableInfo.getFieldName("dept_id") != null;
}
@Override
public String getPermission(MethodInfo methodInfo, TableInfo tableInfo, String alias) {
return alias + ".dept_id=1";
}
})};
}
```
```java
public interface PermissionHandler {
boolean isPermissionInject(MethodInfo methodInfo, TableInfo tableInfo);
String getPermission(MethodInfo methodInfo, TableInfo tableInfo, String alias);
}
```
| 方法名 | 描述 |
|--------------------|----------------------------------------------------------------------------|
| isPermissionInject | 是否对当前查询语句注入where条件,methodInfo:记录了方法的一切信息tableInfo:记录了表的一切信息 |
| getPermission | 插入的where条件,不能为空,methodInfo:记录了方法的一切信息tableInfo:记录了表的一切信息,alias:当前查询语句主表的别名 |
### **逻辑删除**
有些字段是需要进行逻辑删除的,有些字段不需要,区别在于表是否加了逻辑字段,假如:未来有个需求,这个表不需要逻辑删除,另一张表需要逻辑删除,代码修改必不可少,幸运的是有些框架提供了逻辑删除,自动将delete语句改成update语句,代码量基本上无改动,事实上,表与表之间关联条件以及where条件是否都加了逻辑条件,仍然需要一步一步改。
同样的SQL,假设用户表user和文章表都存在逻辑删除字段,改造为逻辑删除
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON b.user_id = u.id
) t_tmp LIMIT 1
```
开启逻辑删除插件
```sql
SELECT *
FROM (
SELECT u.id,
u.NAME,
u.age,
u.email,
b.id bId,
b.NAME bName
FROM USER u
LEFT JOIN blog b ON (b.user_id = u.id)
AND b.del_flag = 0
WHERE u.del_flag = 0
) t_tmp LIMIT 1
```
完成了SQL操作的逻辑字段追加,删除数据库里的逻辑字段就不采用逻辑删除,同样,希望某张表采用逻辑删除,加个逻辑字段即可,代码不需要做任何修改,性能等价于直接写逻辑删除条件,性能无损耗
#### 开启逻辑删除
```java
@Bean
public Inject[] injects() {
return new Inject[]{new LogicInject(() -> "del_flag")};
}
```
```java
public interface LogicHandler {
default boolean isLogic(MethodInfo methodInfo, TableInfo tableInfo) {
return tableInfo.getFieldName(getLogicColumn()) != null;
}
default String getNormalValue() {
return "0";
}
default String getDeletedValue() {
return "1";
}
String getLogicColumn();
}
```
| 方法名 | 描述 |
|-----------------|--------------------------------------------------|
| isLogic | 是否使用逻辑删除methodInfo:记录了方法的一切信息tableInfo:记录了表的一切信息 |
| getNormalValue | 未删除的值 |
| getDeletedValue | 逻辑删除后的值 |
| getLogicColumn | 逻辑删除字段 |
### 数据缓存
dream默认开启基于表的缓存,可重新声明自己的缓存工厂,代替默认工厂即可,不同于其他框架缓存,设计的缓存是基于表的,如果是单系统,且数据修改完全来自框架,可默认开启缓存,对查询的任意SQL都会进行缓存,而且可以保证数据库和缓存一致。
```java
@Bean
public CacheFactory cacheFactory() {
return new DefaultCacheFactory() {
@Override
public Cache getCache() {
return null;
}
};
}
```
既然有了缓存工厂,也可以自定义缓存策略
```java
public interface Cache {
void put(MappedStatement mappedStatement, Object value);
Object get(MappedStatement mappedStatement);
void remove(MappedStatement mappedStatement);
void clear();
}
```
| 方法 | 描述 |
|--------|---------------------------------------------------------------|
| put | mappedStatement:记录了SQL的详尽信息,包括操作的表名,原始SQL的唯一值,执行SQL的唯一值等,存放数据 |
| get | 获取数据 |
| remove | 删除数据 |
| clear | 清空数据 |
### 主键策略
主键策略统一控制全局,自定义主键策略
```java
@Bean
public Sequence sequence() {
return new SnowFlakeSequence();
}
```
```java
public class SnowFlakeSequence extends AbstractSequence {
@Override
protected Object sequence(TableInfo tableInfo) {
Class> type = tableInfo.getPrimColumnInfo().getField().getType();
Long nextId = SnowFlake.nextId();
if (type == Long.class) {
return nextId;
} else if (type == String.class) {
return Long.toHexString(nextId);
} else if (type == Integer.class) {
return nextId.intValue();
}
throw new SoothException("不支持的主键类型:" + type.getName());
}
}
```
开发者随意定义主键策略即可
### 数据填充
存入数据库,采用wrap实现,读取数据库采用Extract实现
### 数据脱敏
存入数据库,采用wrap实现,读取数据库采用Extract实现
### SQL审计
采用监听器自由实现
### SQL打印
采用监听器自由实现
### 字段权限
存入数据库,采用wrap实现,读取数据库采用Extract实现
### 字段加密
存入数据库,采用wrap实现,读取数据库采用Extract实现
### 字典回写
存入数据库,采用wrap实现,读取数据库采用Extract实现
# **问题**
1. 当SQL长度过长,达到几千行以上,翻译报java.lang.StackOverflowError,调大Xss参数
# 联系方式
微信群:
QQ群: