Posts
简易ORM框架
- 此框架适合喜欢JPA注解,但讨厌hibernate和spring data jpa效率低下的.同时又不想失去sql灵活性的.
- 此框架主要基于ef-orm框架理念
- 使用jpa注解,但不完全实现jpa规范,单表增,删,改比较方便,同时对级联也做了支持,但不实现延迟加载功能,必须手动调用,才能加载级联对象(此处主要降低jpa实现复杂度).
- jpa支持注解如下: @Column,@Table,@Entity,@Id,@OneToOne,@OneToMany,@ManyToMany,@ManyToOne.@JoinColumn,@JoinTable,@Version
- 使用了代码增强技术,增强了实体类.需要继承DBObject类.并使用配置实现代码增强.继承DBObject类的java bean 只要调用set方法即可精确修改数据库对象.
- 支持级联配置
- 支持Map格式的数据对象返回(由于不区分字段大小写,要求数据库设计对字段大小写不敏感).
- 支持使用模板写sql,使用enjoy和jetbrick-template实现.
- 支持对象操作的乐观锁功能.
- 支持实体对象生成功能
- 框架主要基于jdbc实现,极其轻量.几乎全部功能都采用单例模式实现.
- 整合支持querydsl,jooq用法,提高系统可维护性.能降低80%~90%的sql硬编码.极大提高系统的可维护性.
- 支持mybatis的resultMap,但无需编写xml映射,实体类只需使用@Column注解和数据库字段映射即可,对于一条sql语句对应一个主类带子类对象,使用@SqlResultSetMapping注解标记即可实现主类、子类的组装.
- 此框架为整合性框架,感谢ef-orm,jfinal,BeetlSQL,Nutz,mybatis,jetbrick-orm
快速预览
- spring 环境下 引入maven ````
配置maven插件
```
<plugin>
<groupId>com.github.atshow</groupId>
<artifactId>orm-maven-plugin</artifactId>
<version>1.0.1</version>
<executions>
<execution>
<goals>
<goal>enhanceASM</goal>
</goals>
</execution>
</executions>
</plugin>
```
```
@Bean
public OrmConfig getOrmConfig(DataSource dataSource) {
DaoTemplate dt = new DaoTemplate(dataSource);
OrmConfig config = new OrmConfig();
config.setDbClient(dt);
config.setPackagesToScan(StringUtils.split("db.domain",","));
config.setDbClient(dt);
config.setUseTail(true);
config.setFastBeanMethod(false);
config.init();
return config;
}
@Bean(name="daoTemplate")
public DaoTemplate geDaoTemplate(OrmConfig config) {
return (DaoTemplate) config.getDbClient();
}
```
2. spring boot直接配置
application.properties中配置
```
#jpa实体类所在的包
smallorm.packages=db.domain
...
```
spring boot中的main方法启动中加入增强的代码
```
public static void main(String[] args) throws Exception {
//jpa实体类所在的包
new EntityEnhancerJavassist().enhance("db.domain");
SpringApplication.run(SefApplication.class, args);
}
```
引入spring-boot-jdbc-starter
3.编写jpa实体类
```
package db.domain;
import sf.database.annotations.Comment;
import sf.database.annotations.FetchDBField;
import sf.database.annotations.Type;
import sf.database.jdbc.extension.ObjectJsonMapping;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.*;
@Entity
@Table(name = "wp_users")
@Comment("用户表")
public class User extends sf.core.DBObject {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "login_name", length = 60, nullable = false)
private String loginName;// 登陆名
@Column(length = 64)
private String password;
@Column(length = 50)
private String nicename;
@Column(length = 100)
private String email;
@Column(length = 100)
private String url;
@Column
@Temporal(TemporalType.TIMESTAMP)
private Date registered;
/**
* 激活码
*/
@Column(name = "activation_key", length = 60, nullable = false)
private String activationKey;
@Column
private int status;
@Column(name = "display_name", length = 250)
@Enumerated(EnumType.STRING)
private Names displayName;
@Column
private Boolean spam;
@Column
private boolean deleted;
@Column(precision = 10,scale = 5)
private BigDecimal weight;
@Transient
private boolean lock;
@Column(name = "maps",length = 1500)
@Type(ObjectJsonMapping.class)
private Map<String,String> maps;
@ManyToMany
@Transient
@OrderBy("id asc,role desc")
@JoinTable(name = "user_role", joinColumns = {
@JoinColumn(name = "user_id", referencedColumnName = "id")}, inverseJoinColumns = {
@JoinColumn(name = "role_id", referencedColumnName = "id")})
private List<Role> roles;
@OrderBy
@Transient
@FetchDBField({"id","key"})
@OneToMany(targetEntity = UserMeta.class)
@JoinColumn(name = "id", referencedColumnName = "userId")
private Set<UserMeta> userMetaSet = new LinkedHashSet<UserMeta>();
public enum Names {
zhangshang, lisi
}
/**
* 普通字段
*/
public enum Field implements sf.core.DBField {
id, loginName, password, nicename, email, url, registered, activationKey, status, displayName,maps, spam, deleted,weight;
}
/**
* 级联字段
*/
public enum CascadeField implements sf.core.DBCascadeField {
roles, userMetaSet
}
public User() {
}
... get set方法
}
```
在dao中引入
```
@Resource
private DaoTemplate dt;
```
以daoTemplate操作sql方法.
* 插入对象
```
User user = dt.selectOne(new User());
User u = new User();
u.setLoginName(UUID.randomUUID().toString());
u.setDeleted(false);
u.setCreated(new Date());
u.setActivationKey("23k4j2k3j4i234j23j4");
//插入对象,生成的语句为:insert into wp_users(activation_key,created,deleted,login_name) values(?,?,?,?)
int i = dt.insert(u);
```
* 执行原生sql
```
String sql = "select * from wp_users";
List<User> list = dt.selectList(User.class, sql);
```
* 执行模板sql
```
#sql("queryUserByName")
select * from wp_users
#where()
#if(id)
and id=#p(id)
#end
#if(username)
and login_name=#p(username)
#end
#if(nicename)
and nicename=#p(nicename)
#end
#if(nicenames)
and nicename #in(nicenames)
#end
#end
#end
```
java代码
```
Map<String, Object> query = new HashMap<>();
query.put("id", 1);
List<User> list2 = dt.selectListTemplate(User.class, "queryUserByName", query);
```
* 执行Querydsl
```
SQLRelationalPath<User> q = QueryDSLTables.relationalPathBase(User.class);
SQLQuery<User> query = new SQLQuery<User>();
query.select(q).from(q).where(q.string(User.Field.displayName).isNotNull())
.orderBy(new OrderSpecifier<>(Order.ASC, q.column(User.Field.id)));
Page<User> page = dt.sqlQueryPage(query,User.class, 2, 3);
```
* 执行jooq代码
```
JooqTable<?> quser = JooqTables.getTable(User.class);
JooqTable<?> qrole = JooqTables.getTable(Role.class);
Select<?> query = DSL.select(quser.fields()).from(quser, qrole).where(quser.column(User.Field.id).eq(1));
User u = dt.jooqSelectOne(query,User.class);
```
## 性能测试图
**普通**
![普通性能测试图](doc/img/Dao性能测试对比_common.png "普通")
**打开快速设值开关**
![快速性能测试图](doc/img/Dao性能测试对比_fast.png "快速")
##2018-12-15 16:17:51 更新
* 1.支持对数据库关键字无需使用标识符.
## 一、概述
### Mybatis的问题
引用大牛的话:Mybatis最大的问题不在于开发效率,而在维护效率上。其过于原生的数据库操作方式,难以避免项目维护过程中的巨大成本。
当数据库字段变化带来的修改工作虽然可以集中到少数几个XML文件中,但是依然会分散在文件的各处,并且你无法依靠Java编译器帮助你发现这些修改是否有错漏。
在一个复杂的使用Mybatis的项目中,变更数据库结构往往带来大量的CodeReview和测试工作,否则难以保证项目的稳定性。
* 1、关联表多时,字段多的时候,sql工作量很大。
* 2、sql依赖于数据库,导致数据库移植性差。
* 3、由于xml里标签id必须唯一,导致DAO中方法不支持方法重载。
* 4、对象关系映射标签和字段映射标签仅仅是对映射关系的描述,具体实现仍然依赖于sql。
* 5、DAO层过于简单,对象组装的工作量较大。
* 6、不支持级联更新、级联删除。
* 7、Mybatis的日志除了基本记录功能外,其它功能薄弱很多。
* 8、编写动态sql时,不方便调试,尤其逻辑复杂时。
* 9、提供的写动态sql的xml标签功能简单,编写动态sql仍然受限,且可读性低。
### Hibernate的问题
* 概念复杂
* 性能不佳
* 优化困难.
### 额外说明
1. 框架在JPA实体类注解基础上,增加枚举字段,用于表示具体的数据库字段和级联字段.
2. 框架使用了静态代码增加技术.做到了真正的实体类值有变化才执行DML(增,删,改)操作
3. 为了兼容各数据库,框架对Map类型的返回,不区分字段大小写,此处要求数据库设计对字段大小写不敏感
4. 为了最大程度支持数据库查询的灵活性,所有的返回值由你决定(实体类,Java基本类型,String,Map,List,Object[]等类型).
5. 框架支持Sqlserver2005及以上版本,Oracle,MySQL,PostgreSQL,SQLite 数据库.
## 二、实体操作
### 实体类继承
为了实现实体的动态更新,数据实体类需要继承:sf.core.DBObject
```
public class XXX extends sf.core.DBObject
```
对于的数据字段需要实现继承:sf.core.DBField接口的枚举
```
public enum Field implements sf.core.DBField{
XXX
}
```
具体可以参数快速开发的中的User类以及sorm-test工程.
此处是为解析表结构做准备,对于数据字段的枚举描述,可以看到后面的querydsl和jooq依赖这些字段.
### 实体类增强.
在上面的例子中,还可以看到spring boot中的代码增强:
```
new EntityEnhancerJavassist().enhance("db.domain");
```
该代码主要是使用javassit对继承了DBObject的实体类做了静态代码增强.框架也同时提供基于ASM的实现
使用maven构建时,可以配置Maven-Plugin,使其在编译完后自动扫描编译路径并执行增强操作。请使用:
### 单表操作
#### 编写jpa实体类
```
package db.domain;
import sf.database.annotations.Comment;
import sf.database.annotations.FetchDBField;
import sf.database.annotations.Type;
import sf.database.jdbc.extension.ObjectJsonMapping;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.*;
@Entity
@Table(name = "wp_users")
@Comment("用户表")
public class User extends sf.core.DBObject {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "login_name", length = 60, nullable = false)
private String loginName;// 登陆名
@Column(length = 64)
private String password;
@Column(length = 50)
private String nicename;
@Column(length = 100)
private String email;
@Column(length = 100)
private String url;
@Column
@Temporal(TemporalType.TIMESTAMP)
private Date registered;
/**
* 激活码
*/
@Column(name = "activation_key", length = 60, nullable = false)
private String activationKey;
@Column
private int status;
@Column(name = "display_name", length = 250)
@Enumerated(EnumType.STRING)
private Names displayName;
@Column
private Boolean spam;
@Column
private boolean deleted;
@Column(precision = 10,scale = 5)
private BigDecimal weight;
@Transient
private boolean lock;
@Column(name = "maps",length = 1500)
@Type(ObjectJsonMapping.class)
private Map<String,String> maps;
@ManyToMany
@Transient
@OrderBy("id asc,role desc")
@JoinTable(name = "user_role", joinColumns = {
@JoinColumn(name = "user_id", referencedColumnName = "id")}, inverseJoinColumns = {
@JoinColumn(name = "role_id", referencedColumnName = "id")})
private List<Role> roles;
@OrderBy
@Transient
@FetchDBField({"id","key"})
@OneToMany(targetEntity = UserMeta.class)
@JoinColumn(name = "id", referencedColumnName = "userId")
private Set<UserMeta> userMetaSet = new LinkedHashSet<UserMeta>();
public enum Names {
zhangshang, lisi
}
/**
* 普通字段
*/
public enum Field implements sf.core.DBField {
id, loginName, password, nicename, email, url, registered, activationKey, status, displayName,maps, spam, deleted,weight;
}
/**
* 级联字段
*/
public enum CascadeField implements sf.core.DBCascadeField {
roles, userMetaSet
}
public User() {
}
... get set方法
}
```
#### 创建Dao操作类
```
// 创建一个数据源
SimpleDataSource dataSource = new SimpleDataSource();
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1/nutzdemo");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 创建一个DBClient实例,在真实项目中, DBClient通常由Spring托管, 使用注入的方式获得.
DBClient dao = new DBClient(dataSource);
// 创建表
dao.createTable(User.class);//如果存在该表则不创建.
User user = new User();
user.setLoginName("ABC");
user.setNicename("CDF");
dao.insert(user);
System.out.println(p.getId());
```
1. 插入和批量插入
对应DBClient中的方法为
```
dao.insert(user);//使用该方法插入后,如果user中的主键将自动被写入user对象中.
//批量插入
List<User> modelList = new ArrayList<>();
....
dao.batchInsert(modelList);
```
除上面的用法,框架也提供了快速的插入和快速批量插入的方法(快速插入都不返回主键)
```
dao.insertFast(user);
//批量插入
List<User> modelList = new ArrayList<>();
....
dao.batchInsertFast(modelList);
```
2. 修改和批量修改
```
//更新对象,如果有查询sql,则按查询sql更新对象(优先级高);如果有主键则按主键查询(优先级低).
User u = new User();
u.setId(1L);
u.setNicename("asdfds");
u.useQuery().createCriteria().eq(User.Field.id,1).and().eq(User.Field.displayName,"222");
dao.update(u);
//批量更新,只支持按主键更新,所以必须设置主键,且所有的需要更新的对象中的属性必须一致.框架使用
//第一个对象中的属性,生成批量更新的执行sql.
List<User> modelList = new ArrayList<>();
....
dao.batchUpdate(modelList);
```
3. 删除和批量删除
```
//删除对象.如果有查询sql,则按查询sql删除对象(优先级高);如果无查询sql,将根据设置的属性,生成删除的sql,使用时请注意.
User u = new User();
u.setId(1L);
u.setNicename("asdfds");
dao.delete(u);
```
或者
```
User u = new User();
u.useQuery().createCriteria().eq(User.Field.id,1).and().eq(User.Field.displayName,"222");
dao.delete(u);
```
```
//批量对象,将根据设置的属性生成删除sql语句,且所有的需要删除的对象中的属性必须一致.框架使用
//第一个对象中的属性,生成批量删除的执行sql.
List<User> modelList = new ArrayList<>();
....
dao.batchDelete(modelList);
```
4. 乐观锁
当实体对象中,有字段使用JAP注解:@Version标注时,框架将为该字段启用乐观锁控制.<br>
支持:整数型,日期类型和字符串类型(字符串类型使用UUID实现)的乐观锁功能.<br>
默认的insert,update方法已经提供了乐观锁功能.另外,框架也提供了其他操作乐观锁的功能.
//具体可以查看api注释.
-
*
- 如果设置了Query条件,按query条件查询。 否则—— *
- 如果设置了主键值,按主键查询,否则—— *
- 按所有设置过值的字段作为条件查询。 *
* 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述
* @param obj
* @param fields 指定字段,控制力度更细,至少一个或多个 描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被插入
* @return
*/
int insertCascade(DBObject obj, DBCascadeField... fields);
/**
* 仅将对象所有的关联字段插入到数据库中,并不包括对象本身
* @param obj 数据对象
* @param fields 字段名称,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被插入
* @return 数据对象本身
* @see javax.persistence.OneToOne
* @see javax.persistence.ManyToMany
* @see javax.persistence.OneToMany
*/
* Java 对象的字段会被保留,这里的删除,将只会删除数据库中的记录
*
* 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述
* @param obj 对象
* @param fields 指定字段,控制力度更细,至少一个或多个 描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被删除
* @param
* Java 对象的字段会被保留,这里的删除,将只会删除数据库中的记录
*
* 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述
* @param obj 数据对象
* @param fields 字段名称,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被删除
* @return 被影响的记录行数
* @see javax.persistence.OneToOne
* @see javax.persistence.ManyToOne
* @see javax.persistence.ManyToMany
*/
* 而这个中间表可能还有其他的字段,比如描述关联的权重等
*
* 这个操作可以让你一次删除某一个对象中多个多对多关联的数据
* @param obj
* @param fields 字段名称,描述了那种多对多关联字段将被执行该操作
* @return 共有多少条数据被更新
* @see javax.persistence.ManyToMany
*/
subscribe via RSS