# dbm
**Repository Path**: jhxx/dbm
## Basic Information
- **Project Name**: dbm
- **Description**: a simple Java ORM Framework, based on spring-jdbc.
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 4
- **Created**: 2017-03-29
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# dbm
------
基于spring jdbc实现的轻量级orm
交流群: 604158262
## 目录
- [特色](https://github.com/wayshall/dbm#特色)
- [示例项目](https://github.com/wayshall/dbm#示例项目)
- [maven配置](https://github.com/wayshall/dbm#maven)
- [一行代码启用](https://github.com/wayshall/dbm#一行代码启用)
- [实体映射](https://github.com/wayshall/dbm#实体映射)
- [BaseEntityManager接口](https://github.com/wayshall/dbm#baseentitymanager接口)
- [CrudEntityManager接口](https://github.com/wayshall/dbm#crudentitymanager接口)
- [DbmRepository查询接口](https://github.com/wayshall/dbm#dbmrepository查询接口)
- [DbmRepository查询接口的多数据源支持](https://github.com/wayshall/dbm#dbmrepository查询接口的多数据源支持)
- [查询映射](https://github.com/wayshall/dbm#查询映射)
- [复杂的嵌套查询映射](https://github.com/wayshall/dbm#复杂的嵌套查询映射)
- [批量插入](https://github.com/wayshall/dbm#批量插入)
- [充血模型支持](https://github.com/wayshall/dbm#充血模型支持)
## 特色
- 基本的实体增删改查(单表)不需要生成样板代码和sql文件。
- 返回结果不需要手动映射,会根据字段名称自动映射。
- 支持sql语句和接口绑定风格的DAO,但sql不是写在丑陋的xml里,而是直接写在sql文件里,这样用eclipse或者相关支持sql的编辑器打开时,就可以语法高亮,更容易阅读。
- 支持sql脚本修改后重新加载
- 内置支持分页查询。
- 接口支持批量插入
- 使用Java8新增的编译特性,不需要使用类似@Param注解标识参数
- 支持多数据源绑定,可以为每个查询接口(DbmRepository)指定具体的数据源
- 支持不同的数据库绑定,查询接口会根据当前绑定的数据源自动绑定加载对应数据库后缀的sql文件
- 提供充血模型支持
## 示例项目
单独使用dbm的示例项目
[boot-dbm-sample](https://github.com/wayshall/boot-dbm-sample)
## maven
当前snapshot版本:4.4.0-SNAPSHOT
若使用snapshot版本,请添加snapshotRepository仓储:
```xml
oss
https://oss.sonatype.org/content/repositories/snapshots/
true
```
添加依赖:
```xml
org.onetwo4j
onetwo-dbm
4.5.0-SNAPSHOT
```
spring的依赖请自行添加。
## 一行代码启用
在已配置好数据源的前提下,只需要在spring配置类(即有@Configuration注解的类)上加上注解@EnableDbm即可。
```java
@EnableDbm
@Configuration
public class SpringContextConfig {
}
```
## 实体映射
```java
@Entity
@Table(name="TEST_USER_AUTOID")
public class UserAutoidEntity {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ID")
protected Long id;
@Length(min=1, max=50)
protected String userName;
@Length(min=0, max=50)
@Email
protected String email;
protected String mobile;
protected UserStatus status;
//省略getter和setter
}
```
### 注意这里用到了一些jpa的注解,含义和jpa一致:
- @Entity,表示这是一个映射到数据库表的实体
- @Table,表示这个实体映射的表
- @Id,表示这是一个主键字段
- @GeneratedValue(strategy=GenerationType.IDENTITY),表示这个主键的值用数据库自增的方式生成,dbm目前只支持IDENTITY和SEQUENCE两种方式
- @Column,表示映射到表的字段,一般用在java的字段名和表的字段名不对应的时候
java的字段名使用驼峰的命名风格,而数据库使用下划线的风格,dbm会自动做转换
注意dbm并没有实现jpa规范,只是借用了几个jpa的注解,纯属只是为了方便。。。
后来为了证明我也不是真的很懒,也写了和@Entity、@Table、@Column对应的注解,分别是:@DbmEntity(@Entity和@Table合一),@DbmColumn。。。
`
注意:为了保持简单和轻量级,dbm的实体映射只支持单表,不支持多表级联映射。复杂的查询和映射请使用[DbmRepository查询接口](https://github.com/wayshall/dbm#dbmrepository查询接口)
`
## BaseEntityManager接口
大多数数据库操作都可以通过BaseEntityManager接口来完成。
BaseEntityManager可直接注入。
先来个简单的使用例子:
```java
@Resource
private BaseEntityManager entityManager;
@Test
public void testSample(){
UserAutoidEntity user = new UserAutoidEntity();
user.setUserName("dbm");
user.setMobile("1333333333");
user.setEmail("test@test.com");
user.setStatus(UserStatus.NORMAL);
//save
Long userId = entityManager.save(user).getId();
assertThat(userId, notNullValue());
//update
String newMobile = "13555555555";
user.setMobile(newMobile);
entityManager.update(user);
//fetch by id
user = entityManager.findById(UserAutoidEntity.class, userId);
assertThat(user.getMobile(), is(newMobile));
//通过实体属性查找,下面的调用相当于sql条件: where mobile=:mobile and status='NORMAL'
user = entityManager.findOne(UserAutoidEntity.class,
"mobile", newMobile,
"status", UserStatus.NORMAL);
assertThat(user.getId(), is(userId));
//使用 querys dsl api,效果和上面一样
UserAutoidEntity queryUser = Querys.from(entityManager, UserAutoidEntity.class)
.where()
.field("mobile").is(newMobile)
.field("status").is(UserStatus.NORMAL)
.end()
.toQuery()
.one();
assertThat(queryUser, is(user));
}
```
BaseEntityManager对象的find开头的接口,可变参数一般都是按键值对传入,相当于一个Map,键是实体对应的属性,值是对应属性的条件值:
entityManager.findOne(entityClass, propertyName1, value1, propertyName2, value2......);
entityManager.findList(entityClass, propertyName1, value1, propertyName2, value2......);
key,value形式的参数最终会被and操作符连接起来。
其中属性名和值都可以传入数组或者List类型的参数,这些多值参数最终会被or操作符连接起来,比如:
- 属性名参数传入一个数组:
```Java
entityManager.findList(entityClass, new String[]{propertyName1, propertyName2}, value1, propertyName3, value3);
```
最终生成的sql语句大概是:
```sql
select t.* from table t where (t.property_name1=:value1 or t.property_name2=:value1) and t.property_name3=:value3
```
- 属性值参数传入一个数组:
```Java
entityManager.findList(entityClass, propertyName1, new Object[]{value1, value2}, propertyName3, value3);
```
最终生成的sql语句大概是:
```sql
select t.* from table t where (t.property_name1=:value1 or t.property_name1=:value2) and t.property_name3=:value3
```
- find 风格的api会对一些特殊参数做特殊的处理,比如 K.IF_NULL 属性是告诉dbm当查询值查找的属性对应的值为null或者空时,该如何处理,IfNull.Ignore表示忽略这个条件。 **
比如:
```Java
entityManager.findList(entityClass, propertyName1, new Object[]{value1, value2}, propertyName3, value3, K.IF_NULL, IfNull.Ignore);
```
那么,当value3(或者任何一个属性对应的值)为nul时,最终生成的sql语句大概是:
```sql
select t.* from table t where (t.property_name1=:value1 or t.property_name1=:value2)
```
property_name3条件被忽略了。
## CrudEntityManager接口
CrudEntityManager是在BaseEntityManager基础上封装crud的接口,是给喜欢简单快捷的人使用的。
CrudEntityManager实例可在数据源已配置的情况下通过简单的方法获取:
```java
@Entity
@Table(name="TEST_USER_AUTOID")
public class UserAutoidEntity {
final static public CrudEntityManager crudManager = Dbms.obtainCrudManager(UserAutoidEntity.class);
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ID")
protected Long id;
@Length(min=1, max=50)
protected String userName;
@Length(min=0, max=50)
@Email
protected String email;
protected String mobile;
protected UserStatus status;
//省略getter和setter
}
```
然后通过静态变量直接访问crud接口:
```Java
UserAutoidEntity.crudManager.save(entity);
UserAutoidEntity user = UserAutoidEntity.crudManager.findOne("userName", userName);
```
## DbmRepository查询接口
DbmRepository查询接口支持类似mybatis的sql语句与接口绑定,但sql文件不是写在丑陋的xml里,而是直接写在sql文件里,这样用eclipse或者相关支持sql的编辑器打开时,就可以语法高亮,更容易阅读。
### 1、定义一个接口
包名:test.dao
```java
@DbmRepository
public interface UserAutoidDao {
@ExecuteUpdate
public int removeByUserName(String userName);
}
```
### 2、定义一个.jfish.sql文件
在resource源码代码文件下新建一个目录:sql
然后在sql目录里新建一个UserAutoidDao全类名的.jfish.sql文件,完整路径和文件为:
sql/test.dao.UserAutoidDao.jfish.sql
文件内容为:
```sql
/*****
* @name: removeByUserName
* 批量删除
*/
delete from test_user_autoid
where 1=1
---这里的userName变量就是接口里的userName参数
[#if userName?has_content]
---这里的userName命名查询参数也是接口里的userName参数
and user_name like :userName
[/#if]
```
解释:
- dbm会根据sql文件名去掉.jfish.sql后缀后作为类名,绑定对应的接口类,此处为:test.dao.UserAutoidDao
- @name: 表示此sql绑定的方法,此处表示会绑定到UserAutoidDao.removeByUserName方法
- \[\#if\]...\[/\#if\],是freemarker的语法,表示条件判断。此处表示,如果userName的值不为空,才生成“user_name like ?” 这个条件
- :userName,spring jdg
- c的命名参数,和接口的方法参数绑定
- @ExecuteUpdate注解表示这个方法会以jdbc的executeUpdate方法执行,实际上可以忽略,因为dbm会识别某些update,insert,delete等前缀的方法名来判断。
### 3、调用
```java
@Service
@Transactional
public class UserAutoidServiceImpl {
@Resource
private UserAutoidDao userAutoidDao;
public int removeByUserName(){
return this.userAutoidDao.removeByUserName("%userName%");
}
}
```
`
提示:如果你不想传入 "%userName%",可以把sql文件里的命名参数“:userName”改成“:userName?likeString”试试,后面的?likeString是调用dbm内置的likeString方法,该方法会自动在传入的参数前后加上'%'。
`
### 其他特性
- 支持通过特殊的注解参数进行查询分派:
```Java
@DbmRepository
public interface UserDao {
public List findUserList(@QueryDispatcher String type);
}
```
dbm会根据QueryDispatcher注解标记的特殊参数的值,分派到不同的sql。
如果type==inner时,那么这个查询会被分派到findUserList(inner);
如果type==outer时,那么这个查询会被分派到findUserList(inner)
sql文件:
```sql
/***
* @name: findUserList(inner)
*/
select
usr.*
from
inner_user usr
/***
* @name: findUserList(outer)
*/
select
usr.*
from
outer_user usr
```
- in条件可以传入List类型的值,会自动解释为多个in参数
DbmRepository接口:
```Java
@DbmRepository
public interface UserDao {
public List findUser(List userNames);
}
```
sql文件:
```sql
/***
* @name: findUser
*/
select
usr.*
from
t_user usr
where
usr.user_name in ( :userNames )
```
- dbm默认会注入一些辅助函数以便在sql文件中调用,可通过_func前缀引用,比如${_func.dateAs(date, "yyyy-MM-dd")}格式化日期。通过QueryConfig注解扩展在sql文件使用的辅助函数集。
sql文件:
```sql
/***
* @name: findUser
*/
select
usr.*
from
t_user usr
where
usr.birthday=${_func.dateAs(date, "yyyy-MM-dd")}
```
## DbmRepository查询接口的多数据源支持
DbmRepository 查询接口还可以通过注解支持绑定不同的数据源:
```Java
@DbmRepository(dataSource="dataSourceName1")
public interface Datasource1Dao {
}
@DbmRepository(dataSource="dataSourceName2")
public interface Datasource2Dao {
}
```
## 查询映射
DbmRepository的查询映射无需任何xml配置,只需要遵循规则即可:
** 1、 **Java类的属性名与sql查询返回的列名一致
** 2、 **或者Java类的属性名采用驼峰命名,而列明采用下划线的方式分隔。如:userName对应user_name
举例:
### 创建一个DbmRepository接口
```Java
@DbmRepository
public interface CompanyDao {
List findCompaniesByLikeName(String name);
List findCompaniesByNames(Collection names);
}
public class CompanyVO {
protected Long id;
protected String name;
protected String description;
protected int employeeNumber;
//省略getter和setter
}
```
### 对应的sql文件CompanyDao.jfish.sql
内容如下:
```sql
/****
* @name: findCompaniesByLikeName
*/
select
comp.id,
comp.name,
comp.description,
comp.employee_number
from
company comp
where
comp.name like :name?likeString
/****
* @name: findCompaniesByNames
*/
select
comp.id,
comp.name,
comp.description,
comp.employee_number
from
company comp
[#if names?? && names?size>0]
where
comp.name in (:names)
[/#if]
```
### 调用代码
```Java
List companies = this.companyDao.findCompaniesByLikeName("测试公司");
companies = this.companyDao.findCompaniesByNames(Collections.emptyList());
companies = this.companyDao.findCompaniesByNames(Arrays.asList("测试公司-1", "测试公司-2"));
```
## 复杂的嵌套查询映射
有时,我们会使用join语句,查询出一个复杂的数据列表,比如包含了company、department和employee三个表。
返回的结果集中,一个company对应多条department数据,而一条department数据又对应多条employee数据,我们希望把多条数据这样的数据最终只映射到一个VO对象里。这时候,你需要使用@DbmResultMapping和@DbmNestedResult两个注解,以指定VO的那些属性需要进行复杂的嵌套映射。
举例如下:
### 创建一个DbmRepository接口和相应的VO
```Java
@DbmRepository
public interface CompanyDao {
@DbmResultMapping({
@DbmNestedResult(property="departments.employees", columnPrefix="emply_", nestedType=NestedType.COLLECTION),
@DbmNestedResult(property="departments", id="id", nestedType=NestedType.COLLECTION)
})
List findNestedCompanies();
}
public class CompanyVO {
protected Long id;
protected String name;
protected String description;
protected int employeeNumber;
protected List departments;
//省略getter和setter
}
public class DepartmentVO {
protected Long id;
protected String name;
protected Integer employeeNumber;
protected Long companyId;
protected List employees;
//省略getter和setter
}
public class EmployeeVO {
protected Long id;
protected String name;
protected Date joinDate;
//省略getter和setter
}
```
解释:
- @DbmResultMapping注解表明,查询返回的结果需要复杂的嵌套映射
- @DbmNestedResult注解告诉dbm,返回的CompanyVO对象中,哪些属性是需要复杂的嵌套映射的。property用于指明具体的属性名称,columnPrefix用于指明,需要把返回的结果集中,哪些前缀的列都映射到property指定的属性里,默认会使用property。nestedType标识该属性的嵌套类型,有三个值,ASSOCIATION表示一对一的关联对象,COLLECTION表示一对多的集合对象,MAP也是一对多,但该属性的类型是个Map类型。id属性可选,配置了可一定程度上加快映射速度。
### 对应的sql
```sql
/*****
* @name: findNestedCompanies
*/
select
comp.*,
depart.id as departments_id,
depart.company_id as departments_company_id,
depart.`name` as departments_name,
emply.name as emply_name,
emply.join_date as emply_join_date,
emply.department_id as emply_department_id
from
company comp
left join
department depart on comp.id=depart.company_id
left join
employee emply on emply.department_id=depart.id
```
### 调用
```Java
List companies = companyDao.findNestedCompanies();
```
## 批量插入
在mybatis里,批量插入非常麻烦,我见过有些人甚至使用for循环生成value语句来批量插入的,这种方法插入的数据量如果很大,生成的sql语句以吨计,如果用jdbc接口执行这条语句,系统必挂无疑。
实际上,jdbc很多年就提供批量插入的接口,在dbm里,使用批量接口很简单。
定义接口:
```java
public interface UserAutoidDao {
public int batchInsert(List users);
}
```
定义sql:

搞掂!
## 充血模型支持
dbm对充血模型提供一定的api支持,如果觉得好玩,可尝试使用。
使用充血模型,需要下面几个步骤:
### 1、需要在Configuration类配置model所在的包位置
单独使用dbm的项目,只要model类在@EnableDbm注解所在的配置类的包(包括子包)下面即可,dbm会自动扫描。
```Java
@EnableDbm
public class DbmSampleApplication {
}
```
若使用jfish的项目,因为用了spring boot的autoconfig,可以使用@DbmPackages注解配置
```Java
@Configuration
@DbmPackages({"org.onetwo4j.sample.model"})
public class AppContextConfig {
}
```
### 2、继承RichModel类
```Java
@Entity
@Table(name="web_user")
public class User extends RichModel {
}
```
### 3、使用api
```Java
//根据id查找实体
User user = User.findById(id);
//保存实体
new User().save();
//统计
int count = User.count().intValue();
//查找, K.IF_NULL属性是告诉dbm当查询值userName为null或者空时,该如何处理。IfNull.Ignore表示忽略
List users = User.findList("userName", userName, K.IF_NULL, IfNull.Ignore);
```
### 待续。。。