简易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注解标记即可实现主类、子类的组装.
- 此为整合性orm,感谢ef-orm,jfinal,BeetlSQL,Nutz,mybatis,jetbrick-orm
- 最低要求jdk8,兼容jdk11.
快速预览
- spring 环境下 引入maven
<!-- 由于版本还在完善中,暂时先使用私仓 --> <repository> <id>sxfmaven</id> <name>local private nexus</name> <url>https://gitee.com/parken/sxfmvnrepo/raw/master/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <!-- 引入jar包 --> <dependency> <groupId>com.github.atshow</groupId> <artifactId>sorm</artifactId> <version>1.0.1</version> </dependency>
配置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();
}
- 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);
性能测试图
普通
打开快速设值开关
##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的问题
- 概念复杂
- 性能不佳
- 优化困难.
- 过度设计
额外说明
- 在JPA实体类注解基础上,增加枚举字段,用于表示具体的数据库字段和级联字段.
- 使用了静态代码增强技术.做到了真正的实体类值有变化才执行DML(增,删,改)操作
- 为了兼容各数据库,对Map类型的返回,不区分字段大小写,此处要求数据库设计对字段大小写不敏感
- 为了最大程度支持数据库查询的灵活性,所有的返回值由你决定(实体类,Java基本类型,String,Map,List,Object[]等类型).
- 支持Sqlserver2005及以上版本,Oracle8,MySQL5,PostgreSQL9,SQLite3 数据库.
二、实体操作
实体类继承
为了实现实体的动态更新,数据实体类需要继承: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,使其在编译完后自动扫描编译路径并执行增强操作。请使用:
<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>
单表操作
编写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());
- 插入和批量插入 对应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);
- 修改和批量修改
//更新对象,如果有查询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);
- 删除和批量删除
//删除对象.如果有查询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);
- 乐观锁 当实体对象中,有字段使用JAP注解:@Version标注时,将为该字段启用乐观锁控制.
- 支持:整数型,日期类型和字符串类型(字符串类型使用UUID实现)的乐观锁功能.
- 默认的insert,update方法已经提供了乐观锁功能.另外,也提供了其他操作乐观锁的功能.
//具体可以查看api注释. <T extends DBObject> int updateAndSet(T obj); <T extends DBObject> int updateWithVersion(T obj);
- 查询 针对model的查询,只需要在实体类中,设置过值,即可.
- 也支持使用Example查询
user.useQuery().createCriteria().eq(User.Field.id, 1).and().eq(User.Field.displayName, "222");
如果使用Example查询,将忽略实体类中设值的查询(按主键查询除外).此查询对更新和删除同样有效.
/** * 根据主键查询 * @param clz 实体类 * @param keyParams 主键参数 * @param <T> 泛型 * @return 实体 */ <T extends DBObject> T selectByPrimaryKeys(Class<T> clz, Object... keyParams); /** * 查询总数 * @param query 查询 * @param <T> 泛型 * @return 实体 */ <T extends DBObject> long selectCount(T query); /** * 查询一条记录,如果结果不唯一则抛出异常 * @param query 查询条件 * @return 查询结果 */ <T extends DBObject> T selectOne(T query); /** * 使用select ... for update 查询数据 * @param query 查询条件 * @param <T> 泛型 * @return 实体 */ <T extends DBObject> T selectOneForUpdate(T query); /** * 查询列表 * @param query 查询请求。 * <ul> * <li>如果设置了Query条件,按query条件查询。 否则——</li> * <li>如果设置了主键值,按主键查询,否则——</li> * <li>按所有设置过值的字段作为条件查询。</li> * </ul> * @return 结果 */ <T extends DBObject> List<T> selectList(T query); /** * 使用select ... for update 查询数据 * @param query 查询 * @param <T> 泛型 * @return 实体 */ <T extends DBObject> List<T> selectListForUpdate(T query); /** * 查询并分页 * @param query 查询请求 * @param start 起始记录,offset。从0开始。 * @param limit 限制记录条数。如每页10条传入10。 * @return 分页对象 */ <T extends DBObject> Page<T> selectPage(T query, int start, int limit); /** * 查询迭代结果.回调形式. * @param ormIt 迭代回调方法 * @param query 查询 * @param <T> 泛型 */ <T extends DBObject> void selectIterator(OrmIterator<T> ormIt, T query); /** * 查询限制条数和起始位置的迭代结果.回调形式. * @param ormIt 迭代回调方法 * @param query 查询 * @param start 起始数 * @param limit 限制数 * @param <T> 泛型 */ <T extends DBObject> void selectIterator(OrmIterator<T> ormIt, T query, int start, int limit); /** * stream lambda形式迭代结果. * @param ormStream 迭代回调方法 * @param query 查询 * @param <T> 泛型 */ <T extends DBObject> void selectStream(OrmStream<T> ormStream, T query);
级联操作
- 级联关系配置 使用jpa注解,级联配置基本上和hibernate一样,唯一的区别在于,级联字段都需要使用Java字段名称,而不是数据库列名. 在jpa注解之外,还添加了额外的几个注解.
//此注解说明,需要抓取的级联对象的字段.主要是适用于,无需全部查询级联对象字段的值 @FetchDBField
- 级联对象插入 未提供单独的级联对象插入功能,可以使用普通的对象插入方法,保存级联对象.
- 级联对象修改 级联对象修改,需要在主对象完整的情况下.使用:
/** * 将对象插入数据库同时,也将指定级联字段的所有关联字段关联的对象统统插入相应的数据库 * <p> * 关于关联字段更多信息,请参看 '@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 */ <T extends DBObject> T insertLinks(T obj, DBCascadeField... fields); /** * 将对象的一个或者多个,多对多的关联信息,插入数据表 * @param obj 对象 * @param fields 正则表达式,描述了那种多对多关联字段将被执行该操作 * @return 对象自身 * @see javax.persistence.ManyToMany */ <T extends DBObject> T insertRelation(T obj, DBCascadeField... fields);
- 级联对象删除
/** * 将对象删除的同时,也将指定级联字段的所有关联字段关联的对象统统删除 <b style=color:red>注意:</b> * <p> * Java 对象的字段会被保留,这里的删除,将只会删除数据库中的记录 * <p> * 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述 * @param obj 对象 * @param fields 指定字段,控制力度更细,至少一个或多个 描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被删除 * @param <T> 泛型 * @return 执行结果 */ <T extends DBObject> int deleteCascade(T obj, DBCascadeField... fields); /** * 仅删除对象所有的关联字段,并不包括对象本身。 <b style=color:red>注意:</b> * <p> * Java 对象的字段会被保留,这里的删除,将只会删除数据库中的记录 * <p> * 关于关联字段更多信息,请参看 '@One' | '@Many' | '@ManyMany' 更多的描述 * @param obj 数据对象 * @param fields 字段名称,描述了什么样的关联字段将被关注。如果为 null,则表示全部的关联字段都会被删除 * @return 被影响的记录行数 * @see javax.persistence.OneToOne * @see javax.persistence.ManyToOne * @see javax.persistence.ManyToMany */ <T extends DBObject> int deleteLinks(T obj, DBCascadeField... fields); /** * 多对多关联是通过一个中间表将两条数据表记录关联起来。 * <p> * 而这个中间表可能还有其他的字段,比如描述关联的权重等 * <p> * 这个操作可以让你一次删除某一个对象中多个多对多关联的数据 * @param obj * @param fields 字段名称,描述了那种多对多关联字段将被执行该操作 * @return 共有多少条数据被更新 * @see javax.persistence.ManyToMany */ <T extends DBObject> int deleteRelation(T obj, DBCascadeField... fields);
- 级联对象查询
/** * 查找对象列表,并查询级联字段 * @param query 查询 * @param clz 实体类 * @param fields 级联字段 * @param <T> 泛型 * @return 列表 */ <T extends DBObject> List<T> fetchCascade(T query, Class<T> clz, DBCascadeField... fields); /** * 查询对象,并返回所有的级联字段的值 * @param obj 实体 * @return 返回带级联字段值得对象 */ <T extends DBObject> T fetchLinks(T obj); /** * 查询单一对象 * @param obj 实体类 * @param fields 级联字段 * @return 对象 */ <T extends DBObject> T fetchLinks(T obj, DBCascadeField... fields);
三、sql操作
此大类方法主要是为了兼容普通jdbc操作,使与JdbcTemplate使用一致.
- 自由设置返回值,此功能主要是改进mybatis中的resultMap书写不方便的问题. 对于实体类,当实体类的字段与sql语句返回的字段不一致时,可以使用@Column注解的name值,设置对应的返回字段. 具体请查看DBMethod类中的方法.
- 支持查询对象拆分到更多的子对象中. 此功能只需要在实体类中,添加一对一的映射,使用JPA的@SqlResultSetMapping注解.
//此注解实现:主要解决子对象的创建问题.
//此处实现:只取第一个EntityResult的配置,并且只识别fields类型字段
@SqlResultSetMapping(name = "UserResult",
entities = {
@EntityResult(entityClass = UserResult.class, fields = {
@FieldResult(name = "metaResult.userId", column = "id"/*对应别名*/),
@FieldResult(name = "metaResult.icon", column = "icon"/*对应别名*/)
}
)
}
)
public class UserResult {
private Long id;
@Column(name = "login_name")
private String loginName;// 登陆名
private String password;
private String nicename;
private MetaResult metaResult;
..... 省略get set
public class MetaResult {
private Long userId;
private String icon;
..... 省略get set
四、模板操作
模板主要使用enjoy模板实现,基本功能和jfinal中的enjoy sql模板一致,主要额外添加了如下指令
- where
#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
- in in中的变量值为Collection类型
#if(nicenames) and nicename #in(nicenames) #end
- pageTag pageTag与pageIgnore主要为分页服务. page和pageTag区别: 如果无参数,则在查询的时候解释成 *,如果有参数,则解释成列名,如 page("a.name,a.id,b.name role_name") ,如果列名较多,可以使用pageTag
#sql("queryUserByNamePage")
select #pageTag("*") 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
#end
#pageIgnoreTag()
order by id
#end
#end
- pageIgnore 标签函数 pageIgnoreTag,用在翻页查询里,当查询用作统计总数的时候,会忽略标签体内容
- use use主要用于复用sql片段.
#sql("user_cols")
id,login_name,nicename
#end
#sql("queryUserByName")
select #use("user_cols") from wp_users
#use("user_condition")
#end
五、整合QueryDSL和jOOQ
整合QueryDSL和JOOQ主要是为了给提供类型安全的查询语句生成,避免代码里出现过多的硬编码sql语句,导致维护噩梦. 使用QueryDSL和JOOQ主要以使用固定表为主.
- QueryDSL 首先需要继承DBObject的数据库实体类,
//此处主要是通过QueryDSLTables获取Q类,QueryDSLTables提供缓存功能. 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.queryDSLSelectPage(query,User.class, 2, 3);
- Jooq 主要支持开源的jooq版本,对于商业版本,可以在生成DML语句时,使用DSL.using(Dialect)解决,将以设置的Dialect执行语句.
//此处主要是通过JooqTables获取Q类,JooqTables提供缓存功能. 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);
六、和spring,spring boot整合
Spring主要使用DaoTemplate类,此类已与Spring jdbc做了集成.
- 和spring 整合
//多数据源配置
@Bean
public OrmConfig getOrmConfig(@Autowired(required = false) @Qualifier("xaMysql") DataSource xaMysql,
@Autowired(required = false) @Qualifier("sqlite") DataSource sqlite,
@Autowired(required = false) @Qualifier("postgresql") DataSource postgresql,
@Autowired(required = false) @Qualifier("oracle") DataSource oracle,
@Autowired(required = false) @Qualifier("sqlserver") DataSource sqlserver) {
SimpleRoutingDataSource routing = new SimpleRoutingDataSource();
routing.getDataSources().put("mysql", sqlite);
routing.getDataSources().put("sqlite", sqlite);
// routing.addDataSource("postgresql", postgresql);
routing.setDefaultKey("mysql");
DaoTemplate dt = new DaoTemplate(routing);
// DaoTemplate dt = new DaoTemplate(xaMysql);
OrmConfig config = OrmConfig.getInstance();
config.setDbClient(dt);
//需要扫描的实体类.此处提供自动建表功能.
config.setPackagesToScan(StringUtils.split("db.domain", ","));
config.setUseTail(true);
config.setBeanValueType(OrmValueUtils.BeanValueType.fast);
config.setUseSystemPrint(false);
config.init();
return config;
}
@Bean(name = "daoTemplate")
public DaoTemplate geDaoTemplate(OrmConfig config) {
return (DaoTemplate) config.getDbClient();
}
- 和spring boot 整合. 以下属性允许在application.properties中配置.
smallorm.packages=db.domain #指定扫描若干包,如果有多个请使用,分隔 smallorm.show-sql=true #sql日志开关 smallorm.bean-value-type=method #使用哪一种bean转换,method,field,unsafe smallorm.use-tail=false #是否使用tail获取额外属性. smallorm.allow-drop-column=false #扫描到实体后,如果准备修改表,如果数据库中的列多余,是否允许删除列 smallorm.create-table=true #扫描到实体后,如果数据库中不存在,是否建表 smallorm.alter-table=true #扫扫描到实体后,如果数据库中存在对应表,是否修改表 smallorm.batch-size=100 #批处理数量 smallorm.enhance-scan-packages=true #对配置了包扫描的路径进行增强检查,方便单元测试 smallorm.open-stream-iterator=false #是否开启流式迭代 smallorm.sql-template-path=classpath*:sql/**/*.sql #sql模板位置 smallorm.sql-template-debug=false #是否开启sql模板调试模式 smallorm.sql-template-type=enjoy #使用的sql模板类型
七、daoMapper功能
框架支持类mybatis的mapper功能.
DBClient 提供了所有需要知道的API,但通过sqlid来访问sql有时候还是很麻烦,sorm支持Mapper,将sql文件映射到一个interface接口。接口的方法名与sql文件的sqlId一一对应。
接口必须实现DaoMapper接口,它提供内置的CRUID方法,如insert,update,merge,delete,unique,selectTemplate,executeTemplate,updateById等
DaoMapper 具备数据库常见的操作,接口只需要定义额外的方法与sqlId同名即可。
@OrmMapper
public interface UserDAOMapper extends DaoMapper<User> {
@ExecuteTemplate(id = "db.user.selectUserByTemplateId")
List<User> selectAllTemplateId(@OrmParam("id") int id);
}
如上select将会对应如下sql文件
#sql("db.user.selectUserByTemplateId")
select * from wp_users where id=#p(id)
#end
你使用必须为JDK8,可以不必为参数提供名称,框架能自动对应。但必须保证java编译的时候开启-parameters选项。如果未开启编译参数,则可以使用@OrmParam注解()
@ExecuteTemplate
List<User> selectUserByTemplateId(@OrmParam("id") int id);
sorm的mapper方法会根据调用方法名字,返回值,以及参数映射到DBClient相应的查询接口,比如返回类型是List,意味着发起DBClient.selectTemplate 查询,如果返回是一个Map或者Pojo,则发起一次selectOneTemplate查询,如果返回定义为List,则表示查询实体,如果定义为List ,则对应的查询结果映射为Long
定义好接口后,可以通过DBClient.getMapper 来获取一个Dao真正的实现
UserDAOMapper dao = dbClient.getMapper(UserDAOMapper.class);
如果你使用Spring或者SpringBoot,可以参考Spring集成一章,了解如何自动注入Mapper
Mapper 对应的sql文件默认根据配置文件名来确定,可以通过@SqlResource 注解来指定Mapper的id前缀。比如
@OrmMapper
@SqlResource("db.user")
public interface UserCoreDao extends DaoMapper<User> {
@ExecuteTemplate
List<User> select(String name);
}
@OrmMapper
@SqlResource("db.userConsole")
public interface UserConsoleDao extends DaoMapper<User> {
@ExecuteTemplate
List<User> select(String name);
}
这样,这俩个mapper分别访问db.user.select 和 db.userConsole.select
7.1. 内置CRUD DaoMapper包含了内置的常用查询,如下
public interface DaoMapper<T extends DBObject> {
SQLRelationalPath<T> queryDSLTable();
JooqTable<?> jooqTable();
/**
* 合并记录
* @param entity
* @return
*/
int merge(T entity);
/**
* 通用插入,插入一个实体对象到数据库,返回主键
* @param entity
*/
int insert(T entity);
/**
* 批量插入实体
* @param list
* @param fast 是否快速插入 如果是将不返还主键
*/
int[] insertBatch(List<T> list, boolean fast);
/**
* 根据主键更新对象,对象set过才被更新
* @param entity
* @return
*/
int updateById(T entity);
int[] updateBatch(List<T> list);
int delete(T entity);
/**
* 根据主键删除对象,如果对象是复合主键,传入对象本生即可
* @param key
* @return
*/
int deleteById(Object... key);
int[] deleteInBatch(Iterable<T> entities);
void deleteAllInBatch();
/**
* 根据主键获取对象,如果对象不存在,则会抛出一个Runtime异常
* @param keys
* @return
*/
T unique(Object... keys);
/**
* 根据主键获取对象,如果对象不存在,返回null
* @param query
* @return
*/
T single(T query);
/**
* 根据主键获取对象,如果在事物中执行会添加数据库行级锁(select * from table where id = ? for
* update),如果对象不存在,返回null
* @param query
* @return
*/
T lock(T query);
/**
* 返回实体在数据库里的总数
* @return
*/
long count();
boolean existsById(Object... id);
boolean exists(T query);
/**
* 返回实体对应的所有数据库记录
* @return
*/
List<T> selectList(T query);
/**
* 返回实体对应的一个范围的记录
* @param start
* @param size
* @return
*/
Page<T> selectPage(T query, int start, int size);
//////////////////// queryDSL /////////////
/**
* @param query
* @return
*/
List<T> queryDSLSelectList(AbstractSQLQuery<T, SQLQuery<T>> query);
/**
* @param query
* @param start
* @param limit
* @return
*/
Page<T> queryDSLSelectPage(AbstractSQLQuery<T, SQLQuery<T>> query, int start, int limit);
/**
* @param query
* @param clz 返回值
* @param <S>
* @return
*/
<S> S queryDSLSelectOne(AbstractSQLQuery<T, SQLQuery<T>> query, Class<S> clz);
/**
* @param query
* @return
*/
T queryDSLSelectOne(AbstractSQLQuery<T, SQLQuery<T>> query);
/**
* @param ormIt
* @param query
*/
void queryDSLSelectIterator(OrmIterator<T> ormIt, AbstractSQLQuery<T, SQLQuery<T>> query);
/**
* @param ormStream
* @param query
*/
void queryDSLSelectStream(OrmStream<T> ormStream, AbstractSQLQuery<T, SQLQuery<T>> query);
//////////////// jooq ////////////////////
/**
* @param select
* @return
*/
T jooqSelectOne(Select<?> select);
<S> S jooqSelectOne(Select<?> select, Class<S> clz);
/**
* @param select
* @return
*/
List<T> jooqSelectList(Select<?> select);
/**
* @param countSelect
* @param pageSelect
* @return
*/
Page<T> jooqSelectPage(Select<?> countSelect, Select<?> pageSelect);
/**
* @param ormIt
* @param select
*/
void jooqSelectIterator(OrmIterator<T> ormIt, Select<?> select);
/**
* @param ormStream
* @param select
*/
void jooqSelectStream(OrmStream<T> ormStream, Select<?> select);
////////////////////// sql 模板执行 //////////////////////
/**
* 执行一个jdbc sql模板查询
* @param sql
* @param paras
* @return
*/
List<T> selectTemplate(String sql, Map<String, Object> paras);
/**
* 执行一个更新的jdbc sql
* @param sql
* @param paras
* @return
*/
int executeTemplate(String sql, Map<String, Object> paras);
//////////>>>>>>>>>>>>>>其他<<<<<<<<<<<<<<<<<<<
/**
* @return
*/
DBClient getDbClient();
/**
* @return
*/
Class<T> getDomainClass();
}
7.2 sql语句 7.2.1 查询 @ExecuteSQL 执行原生sql语句查询,必须在方法上有@ExecuteSQL注解.
@ExecuteSQL(value = {"select * from wp_users"})
List<User> selectAll();
分页查询需要使用RowLimit限制对象查询个数
@ExecuteSQL(value = {"select * from wp_users"})
Page<User> selectSqlPage(RowLimit rl);
方法参数需要与"?" 一一对应
7.2.2 执行插入 对返回的主键需要使用 @SelectKey 该主键主要处理返回值的类型 以下是执行插入语句
@ExecuteSQL(value = {"insert into wp_users(login_name,activation_key) values(?,?)"})
@SelectKey(keyColumn = "id", resultType = long.class)
long insertUser(String loginName, String activeKey);
7.2.3 更新,删除和插入类似,唯一的区别是无返回值.
7.4 sql模板 @ExecuteTemplate 主要提供根据模板id执行模板语句以及直接执行模板语句.
@ExecuteTemplate(value = {"select * from wp_users where id =#p(id)"})
User selectSqlOneTemplate(@OrmParam("id") int id);
插入的返回值
@ExecuteTemplate(value = {"insert into wp_users(login_name,activation_key) values(#p(loginName),#p(activeKey))"}, type = DMLType.INSERT)
@SelectKey(keyColumn = "id", resultType = Map.class)
Map insertUserTemplate(@OrmParam("loginName") String loginName, @OrmParam("activeKey") String activeKey);
7.5 自动生成sql语句 @AutoSQL 标识该方法能自动生成sql, 使用该功能必须引入spring-data-commons依赖,框架使用spring-data-commons生成sql语句
@AutoSQL
List<User> findDistinctUserByIdOrderByLoginNameDesc(@Param("id") int id);
此处的@Param为spring-data-commons中的类.
7.6 额外功能 如果你觉得以上注解过于麻烦,还有一种用法,你可以继承DaoMapper接口,然后再实现该扩展接口即可.
public interface UserDAO extends DaoMapper<User> {
}
@Repository
public class UserDAOImpl extends DaoMapperImpl<User> implements UserDAO {
public UserDAOImpl(@Autowired DBClient dbClient) {
super(User.class, dbClient);
}
}
八、其他功能
- 多数据源,DBClient中使用如下方法,即可切换数据源.
/** * 执行sql上下文,比如切换数据源 * @param dataSource 数据源名称 * @return DBClient */ DBClient useContext(String dataSource);
- 代码生成
/**
* 根据表名生成对应的pojo类
* @param pkg 包名,如 com.test
* @param srcPath: 文件保存路径
* @param config 配置生成的风格
*/
void genPojoCodes(String pkg, String srcPath, GenConfig config);
GenConfig genConfig = new GenConfig();
dbClient.genPojoCodes("target", System.getProperty("user.dir") + File.separator + "com/test/abc", genConfig);
- 分库分表功能 请使用第三方如:sharing-jdbc,mycat等整合.

本文暂时没有评论,来添加一个吧(●'◡'●)