10 Star 22 Fork 4

Show / sorm

Create your Gitee Account
Explore and code with more than 6 million developers,Free private repositories !:)
Sign up
Clone or download
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README.md

Small ORM

sorms是一个全功能orm工具, 同时具有Hibernate与Mybatis的优点。该框架主要适合使用Spring,Spring boot的用户

  • 开发效率
    • 全面拥抱JPA注解,并通过对注解的扩展和JPA实体类的增强,能极其方便的完成单表增删改查功能.
    • 数据模型支持Pojo,Map/List
    • 支持自动建表功能.创建完成JPA实体类,运行即可创建数据库表
    • 支持级联功能,但不实现延迟加载功能(需要通过手动调用,才能加载级联对象,此处主要是为了降低jpa级联实现的复杂度).
    • 支持Map格式的数据对象返回(框架不区分字段大小写,要求数据库设计对字段大小写不敏感).
    • 支持乐观锁功能.
    • 支持SQL查询转换为实体对象功能
    • 支持类似mybatis的resultMap,但无需编写xml映射,实体类只需使用@Column注解和sql返回字段一一对应即可,对于sql语句返回值映射到对象中有子类的情况,可以通过使用@SmallResults注解标记来实现主类、子类的组装.
    • 支持实体类中的类似mybatis Example查询
    • 集成支持querydsl,jooq用法,能降低80%~90%的sql硬编码.极大提高系统的可维护性.
    • SQL模板功能主要基于enjoy实现,更容易编写和调试,以及额外的扩展.
    • 可以针对单个表(或者视图)代码生成pojo类
  • 维护性
    • 使用类型安全的查询,当修改数据库表字段时,可以通过编译提示方便修改类字段.避免mybatis修改数据库字段带来的调试风险.
    • 无缓存功能,避免hibernate,mybatis带来的缓存一致性问题.
    • SQL以enjoy模板管理,支持自定义文件位置,方便程序开发和数据库SQL调试。
    • 可以自动将sql文件映射为dao接口类
  • 其他
    • 框架基于JPA实体类加枚举字段的理念开发而成.
    • JPA注解必须写在字段上,不支持写在方法上.
    • 支持的JPA注解如下: @Column,@Table,@Entity,@Id,@OneToOne,@OneToMany,@ManyToMany,@ManyToOne.@JoinColumn,@JoinTable,@Version,@MapKey,@SqlResultSetMapping,SqlResultSetMappings
    • 使用了代码增强技术,增强了实体类.能精确修改数据库对象(需要继承DBObject类.并使用插件实现代码增强.继承DBObject类的java bean 只要调用普通的set方法即可).
    • 支持主从数据库配置,并支持分库分表的中间件如sharding-jdbc和mycat
    • 性能远超Hibernate,MyBatis
    • 支持跨数据库平台,开发者所需工作减少到最小,目前跨数据库支持mysql,postgres,oracle,sqlserver,sqlite.
    • 支持脱离Spring环境独立运行.
    • 主要基于jdbc实现,极其轻量.几乎全部功能都采用单例模式实现.
    • 此为融合性orm,感谢ef-orm,jfinal,BeetlSQL,Nutz,mybatis,jetbrick-orm
    • 最低要求jdk8,兼容jdk11.源码请使用jdk8编译

快速预览

  1. spring 环境下 引入maven
<!-- 引入jar包 -->
<dependency>
    <groupId>com.github.atshow</groupId>
    <artifactId>sorm</artifactId>
    <version>最新版本</version>
</dependency>

配置maven插件

<plugin>
    <groupId>com.github.atshow</groupId>
    <artifactId>sorm</artifactId>
    <version>最新版本</version>
    <executions>
        <execution>
            <goals>
                <goal>enhanceJavassist</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();
    }
  1. 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.getJooq().jooqSelectOne(query,User.class);

性能测试图

测试 性能测试图

##2018-12-15 16:17:51 更新

  • 1.支持对数据库关键字无需使用标识符.

一、概述

框架诞生初衷:Hibernate和Mybatis各走极端,在笔者实际使用中,Hibernate和Mybatis带来了极大的维护问题,导致大家有很多无意义的加班.

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仍然受限,且可读性低。

参考:https://zhuanlan.zhihu.com/p/45044649

Hibernate的问题

  • 概念复杂
  • 性能不佳
  • 使用优化困难.
  • 过度设计

额外说明

  1. 在JPA实体类注解基础上,增加了枚举字段,用于表示具体的数据库字段和级联字段.
  2. 使用了静态代码增强技术.做到了真正的实体类值有变化才执行DML(增,删,改)操作
  3. 为了兼容各数据库,对Map类型的返回,不区分字段大小写,此处要求数据库设计对字段大小写不敏感
  4. 为了最大程度支持数据库查询的灵活性,所有的返回值由你决定(实体类,Java基本类型,String,Map,List,Object[]等类型).
  5. 数据库支持:支持Sqlserver2005及以上版本,Oracle8,MySQL5,PostgreSQL9,SQLite3 数据库.

二、实体操作

实体类继承

为了实现实体类的动态更新,数据实体类需要继承:sf.core.DBObject

public class XXX extends sf.core.DBObject 

DBObject类做了特殊设计:在json序列化时,不会序列化不相关的属性. 对于的数据字段需要实现继承:sf.core.DBField接口的枚举

public enum Field implements sf.core.DBField{
    XXX
}

具体可以参考快速开发中的User类以及sorm-test工程. 此处是为解析表结构做准备,对于数据字段的枚举描述,可以看到后面的Example查询,以及querydsl,jooq集成依赖这些字段.

实体类创建

使用标准的JPA注解,框架中添加了额外的几个注解,用于补充JPA的表创建
@Comment 用来定义数据库中的字段和表的注释。
@SavedDefaultValue 插入未设置值时使用对象属性的默认值(比如默认的初始化的值).
@FetchDBField 级联需要抓取的字段,只对级联关系生效
@SmallEntity 用于描述一个实体的行为
@SmallResults 结果集描述,主要用于SQL结果集映射到对象中的子对象.
@Tail 将未匹配的字段塞入map中.必须是Map类型上才能使用该注解,主要用于对象返回
@Type 扩展数据类型的注解,可用于支持新的数据映射方式
@UniqueKeyGenerator 唯一键生成策略,只对标注@Id的生效
@ColumnDefine 定义字段类型,如无符号整形
@CreatedDate 插入时自动设置当前时间
@LastModifiedDate 插入时自动设置当前时间和修改时自动设置当前时间

实体类增强.

在上面的例子中,还可以看到spring boot中的代码增强:

new EntityEnhancerJavassist().enhance("db.domain");

该代码主要是使用javassit对继承了DBObject的实体类做了静态代码增强. 代码增强主要是对各个数据库字段的set方法做了增强,如下代码:

@Column(length = 64)
private String password;
...
public void setPassword(String password) {
   this.password = password;
}

增强后代码变为:

public void setPassword(String password) {
    if (this._recordUpdate) {//此处可实现当对象有set值后,即可更新或插入对应的值(无论是否为空).
        this.prepareUpdate(User.Field.password, password);
    }
   this.password = password;
}

同时也提供基于ASM的实现 使用maven构建时,可以配置Maven-Plugin,使其在编译完后自动扫描编译路径并执行增强操作。请使用:

 <plugin>
    <groupId>com.github.atshow</groupId>
    <artifactId>sorm</artifactId>
    <version>最新版本号</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());
  1. 插入和批量插入 对应DBClient中的方法为
dao.insert(user);//使用该方法插入后,主键值将自动被写入user对象中.
//批量插入
List<User> modelList = new ArrayList<>();
....
dao.batchInsert(modelList);

除上面的用法,也提供了快速单一插入和快速批量插入的方法(快速插入都不返回主键)

dao.insertFast(user);
//批量插入
List<User> modelList = new ArrayList<>();
....
dao.batchInsertFast(modelList);
  1. 修改和批量修改
//更新对象,如果有查询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);
  1. 删除和批量删除
//删除对象.如果有查询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);
  1. 乐观锁 当实体对象中,有字段使用JAP注解:@Version标注时,将为该字段启用乐观锁控制.
    支持:整数型,日期类型和字符串类型(字符串类型使用UUID实现)的乐观锁功能.
    默认的insert,update方法已经提供了乐观锁功能.另外,也提供了其他操作乐观锁的功能.
//具体可以查看api注释.
<T extends DBObject> int updateAndSet(T obj);
<T extends DBObject> int updateWithVersion(T obj);
  1. 查询 针对model的查询,只需要在实体类中,设置过值即可.
    也支持使用Example查询
user.useQuery().createCriteria().eq(User.Field.id, 1).and().eq(User.Field.displayName, "222");

如果使用Example查询,将忽略实体类中设值的查询(按主键查询除外).此查询对更新和删除同样有效. 更详细的方法注释可以查看DBMethod类.

//根据主键查询
<T extends DBObject> T selectByPrimaryKeys(Class<T> clz, Object... keyParams);

//查询总数
<T extends DBObject> long selectCount(T query);

//查询一条记录,如果结果不唯一则抛出异常
<T extends DBObject> T selectOne(T query);

//使用select ... for update 查询数据
<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 实体
 */
//使用select ... for update 查询数据
<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);

//查询迭代结果.回调形式.
<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形式迭代结果.
<T extends DBObject> void selectStream(OrmStream<T> ormStream, T query);

级联操作

  1. 级联关系配置 使用jpa注解,级联配置基本上和hibernate一样,唯一的区别在于,级联字段注解配置都需要使用Java字段名称,而不是数据库列名. 注意:框架对级联处理无延迟加载功能. 在jpa注解之外,还添加了额外的几个注解.
//此注解说明,需要抓取的级联对象的字段.主要是适用于,无需全部查询级联对象字段的值
@FetchDBField
  1. 级联对象插入 未提供单独的级联对象插入功能,可以使用普通的对象插入方法,保存级联对象.

  2. 级联对象修改 级联对象修改,需要在主对象完整的情况下.使用:

/**
 * 将对象插入数据库同时,也将指定级联字段的所有关联字段关联的对象统统插入相应的数据库
 * <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);
  1. 级联对象删除
/**
 * 将对象删除的同时,也将指定级联字段的所有关联字段关联的对象统统删除 <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);
  1. 级联对象查询
/**
 * 查找对象列表,并查询级联字段
 * @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);

Example 查询

该查询和Mybatis-generator中的Example类似,主要是参考tk.mybatis.mapper 实现. 简单使用

User user=new User();
user.useQuery().createCriteria().eq(User.Field.id, 1).and().eq(User.Field.displayName, "222");
List<User> list = dt.selectList(user);

Example支持查询,修改,删除功能. 注意:为了提高Example的灵活性,Example中添加了and(),or(),leftP(),rightP() 分别对应:and,or,左括号,右括号等.

三、sql操作

此大类方法主要是为了兼容普通jdbc操作,使与JdbcTemplate使用一致.

  1. 自由设置返回值,此功能主要是改进mybatis中的resultMap书写不方便的问题. 对于实体类,当实体类的字段与sql语句返回的字段不一致时,可以使用@Column注解的name值,设置对应的返回字段. 具体请查看DBMethod类中的方法.

  2. 支持查询对象拆分到更多的子对象中. 此功能只需要在实体类中,添加一对一的映射,使用@SmallResults注解. @Tail 是否将未匹配的字段塞入map中.必须是Map类型上才能使用该注解

//此注解实现:主要解决子对象的创建问题.
@SmallResults({
    @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;

    @Tail
    private Map<String,Object> map;
    .....  省略get set 
public class MetaResult {
    private Long userId;
    private String icon;
    ..... 省略get set

四、模板操作

模板主要使用enjoy模板实现,基本功能和jfinal中的enjoy sql模板一致,框架只支持Map类型参数传入.文档地址: https://jfinal.com/doc
enjoy 支持 单行注释:### XXXX
多行注释: #-- XXX --#
SQL语句支持/..../,-- 注释符号,建议尽量使用多行注释.

#sql("user_cols")
   id,login_name,nicename
#end
#sql("user_condition")
   #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
#sql("queryUserByName")
select #use("user_cols") from wp_users
    #use("user_condition")
#end

//java调用
Map<String, Object> params = new HashMap<>();
params.put("id", 1);
List<Object> nicenames = Arrays.asList("1", "aa");
params.put("nicenames", nicenames);
List<User> list = dt.selectListTemplate(User.class, "queryUserByName", params);

模板支持跨数据库

#sql("queryUserByName.mysql")
   ...
#end
// 对于mysql数据库框架会查找模板id为 mysql.queryUserByName 的模板内容,如果未找到,则会使用默认的queryUserByName
//支持:oracle, sqlserver, db2, derby, postgresql, mysql, mariadb, hsqldb, access, gbase, sqlite, mongo, h2, cubrid, firebird
// 注意都为小写.
dt.selectListTemplate(User.class, "queryUserByName", params)

模板指令

  1. p和para:此二处指令主要标识需要作参数替换.p和para等效
id=#p(id) 
  1. where
    结束需要带#end
#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
  1. in in中的变量值为Collection类型
#if(nicenames)
and nicename #in(nicenames)
#end

not in 可以使用组合方式实现

#if(nicenames)
and nicename  not #in(nicenames)
#end
  1. page page,pageTag;pageIgnore,pageIgnoreTag主要为分页服务. page和pageTag区别:#page为函数调用,#pageTag需要使用#end标签 如 #page("a.name,a.id,b.name role_name") ;如果列名较多,可以使用pageTag, 如:#pageTag() a.name,a.id,b.name role_name #end
#sql("queryUserByNamePage")
select #page("*") 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
  1. pageIgnore 标签函数 pageIgnore和pageIgnoreTag,用在翻页查询里,当查询用作统计总数的时候,会忽略标签体内容; 二者区别和page和pageTag相同
#pageIgnore("order by id")
#pageIgnoreTag()
    order by id
#end
  1. use use主要用于复用sql片段.
#sql("user_cols")
   id,login_name,nicename
#end

#sql("queryUserByName")
select #use("user_cols") from wp_users
    #use("user_condition")
#end
  1. namespace namespace 指令为 sql 语句指定命名空间,配合 @SqlResource 可以在模板mapper中定义命名空间
#namespace("db.user")
    #sql("selectUserByTemplateId")
        select * from wp_users where id=#p(id)
    #end
#end

五、整合QueryDSL,jOOQ,mybatis-dynmic-sql

整合QueryDSL和JOOQ主要是为了提供类型安全的查询语句的生成,避免代码里出现过多的硬编码sql语句,导致维护噩梦. 框架主要以使用固定表为主.

  1. QueryDSL 地址:http://www.querydsl.com/ ( https://github.com/querydsl/querydsl )
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-sql</artifactId>
</dependency>

首先需要继承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.getQueryDSL().queryDSLSelectPage(query,User.class, 2, 3);
  1. Jooq 地址:https://www.jooq.org/ ( https://github.com/jOOQ/jOOQ ) 支持开源和商业的jooq版本;商业版本可以在生成语句时,使用DSL.using(Dialect)方法设置Dialect,框架将以设置的Dialect执行语句.
<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>jooq</artifactId>
</dependency>
//此处主要是通过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.getJooq().jooqSelectOne(query,User.class);

3.mybatis-dynmic-sql 具体介绍请看:https://mybatis.org/mybatis-dynamic-sql/docs/introduction.html ( https://github.com/mybatis/mybatis-dynamic-sql ) 为mybatis官方产品 引入对应的jar包即可

<dependency>
    <groupId>org.mybatis.dynamic-sql</groupId>
    <artifactId>mybatis-dynamic-sql</artifactId>
    <version>1.1.4</version>
</dependency>
List<String> list = Arrays.asList("1", "2", "3");
SimpleTable simpleTable = new SimpleTable(null, null, User.class);
SelectStatementProvider ssp = SelectDSL.select(simpleTable.c(User.Field.id)).from(simpleTable)
        .where(simpleTable.c(User.Field.sex), SqlBuilder.isEqualTo("M"))
        .and(simpleTable.column(User.Field.loginName), SqlBuilder.isLike("%admin%"))
        .and(simpleTable.column(User.Field.nicename), SqlBuilder.isIn(list))
        .build().render(EnjoyRenderingStrategy.instance);
List<User> results = dt.getDynmicSQL().selectList(User.class, ssp);

六、和spring,spring boot整合

Spring主要使用DaoTemplate类,此类已与Spring jdbc做了集成.

  1. 和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();
}
    
  1. 和spring boot 整合. 框架已对spring boot做了自动适配,无需再次手动创建daoTemplate,以下属性允许在application.properties中配置.
smallorm.packages=db.domain                         #指定扫描若干包,如果有多个请使用,分隔(默认以当前Application类所在路径)
smallorm.show-sql=true                              #sql日志开关
smallorm.bean-value-type=method                     #使用哪一种bean转换,method(默认),field,reflectasm 
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模板类型(默认)
smallorm.look-dialect-class=XXXX                    #自定义方言查找方法
smallorm.check-enhancement=true                     #是否需要检查类增强
smallorm.sql-template-paths.enjoy=XXXX              #具体的sql模板位置,如enjoy,freemarker,mybatis等可以单独配置

七、daoMapper功能

框架支持类似mybatis的mapper功能. DBClient 提供了所有需要知道的API,但通过sqlid来访问sql有时候还是很麻烦,sorm支持Mapper,将sql文件映射到一个interface接口。接口的方法名与sql文件的sqlId一一对应。 接口必须实现DaoMapper接口,它提供了内置的CRUD方法,如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> {
......
    int merge(T entity);
    int insert(T entity);
    int update(T entity);
    int delete(T entity);
......
}

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);

方法参数需要与"?" 一一对应 对于实体类中没有对应属性的值,可以创建Map 打上@Tail注解,将额外的属性写入Map中.

 @Tail
 private Map<String,Object> map;

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);
    }
}

如果你在JDK1.8及以上,你也可以直接在接口中编写default方法,使用此方法,无需再如上添加实现类.

default User getUserByLoginName(String loginName) {
    User user = new User();
    user.useQuery().createCriteria().eq(User.Field.loginName, loginName);
    return getDbClient().selectOne(user);
}

八、其他功能

  1. 映射的高级用法 对类JPA实体类中的字段为集合,Map类型时,支持将集合,Map转为JSON字符串存入数据库字段中,查询时,能将json字段转换为对象中集合字段. 需要手动引入对应的json依赖(fastjson,gson,jackson) 以下三种写法效果一致.
@Column(name = "maps1", length = 1500)
@Type(ObjectJacksonMapping.class)//使用jackson
private Map<String, String> maps1;
@Column(name = "maps1", length = 1500)
@Type(ObjectFastJsonMapping.class)//使用fastjson
private Map<String, String> maps1;
@Column(name = "maps2")
@Type(javaType = String.class, sqlType = Types.CLOB)
@Convert(converter = ObjectJsonConverter.class)//使用jpa的转换类
private Map<String, String> maps2;

1.1 @MapKey 该注解用于将级联的子对象集合转换为Map类型,可以设置子对象的哪个字段作为key.默认使用主键.

@OrderBy
@OneToMany(targetEntity = UserMeta.class)
@JoinColumn(name = "id", referencedColumnName = "userId")
@MapKeyClass(Integer.class)
@MapKey
private Map<Integer,UserMeta> metaMap = new HashMap<>();

1.2 @SqlResultSetMapping,SqlResultSetMappings 此两注解用于注册sql语句的返回结果,和JAP用法相同.主要用于以下方法:

//根据@SqlResultSetMapping设置返回值,返回的为单个对象,或数组对象
List<?> selectList(String resultSetMapping, String sql, Object... parameters);

//根据@SqlResultSetMapping设置返回值,返回的为单个对象,或数组对象
List<?> selectList(String resultSetMapping, long start, int limit, String sql, Object... parameters);

//根据@SqlResultSetMapping设置返回值,返回的为单个对象,或数组对象
Object selectOne(String resultSetMapping, String sql, Object... parameters);

注意:如果返回是多个对象,返回值为Object[] 数组,顺序是@SqlResultSetMapping定义的@EntityResult,@ConstructorResult.
2. 多数据源,DBClient中使用如下方法,即可切换数据源.

/**
 * 执行sql上下文,比如切换数据源
 * @param dataSource 数据源名称
 * @return DBClient
 */
DBClient useContext(String dataSource);
  1. 代码生成
/**
 * 根据表名生成对应的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);
  1. 分库分表,主从功能 分库分表请使用第三方如:sharing-jdbc,mycat等整合. 主从功能:框架支持主从功能,在创建DaoTemplate时,请使用
SimpleRoutingDataSource routing = new SimpleRoutingDataSource("sqlite",sqlite,null);
DaoTemplate dt = new DaoTemplate(routing);

对于多数据源,请创建多个DaoTemplate实例即可.

  1. 接入jdbcTemplate 该功能主要用于增强jdbcTemplate查询返回,避免写一堆的rs.getXXXX(...).
 String sql = "SELECT * FROM wp_users";
 List<User> list = jdbcTemplate.query(sql, SormBeanPropertyRowMapper.newInstance(User.class));
  1. 接管Mybatis中XML的sql 该功能主要在混用Mybatis和sorm时,提供运行时接管Mybatis中的SQL功能.
Map<String, Object> map = new HashMap<>();
map.put("nickname", "213");
map.put("loginName", "13213");
map.put("activationKey", "sdfdsf");
boundSql = MybatisTemplate.getSqlParam(sqlSessionFactory.getConfiguration(), "sf.mapper.UserMapper.findByUsernameOrNickName", map);
List<User> list = dt.selectList(User.class, boundSql.getSql(), boundSql.getParams().toArray());

FAQ

  • 模板里没有not in指令
    答:可以使用 not #in(..) 合并使用
  • JPA实体类如何更新主键
    答:通过在对象中设置新主键值,并使用useQuery设置老的主键即可.
User u = new User();
u.setId(23);
u.useQuery().createCriteria().eq(User.Field.id, 1);
dt.update(u);
  • 跨工程引用父类使用enhanceJavassist增强实体类时报错.
    答:请使用enhanceASM增强.
  • 使用JPA实体类构造方法无法保存或修改数据库记录
    答:必须调用对象的set方法,使用构造方法无法设置对象的变更信息.
  • 是否支持多主键
    答:支持,多个主键使用@Id标注即可,免去了JPA复合主键定义的麻烦.
  • 框架是否有缓存功能
    答:未提供缓存功能,可以在业务层通过spring提供的缓存功能实现.此处考虑到添加缓存后,会带来更多的优化调试风险.
  • 框架是否有拦截器功能
    答:无,如果有什么更便捷的使用方法,而框架未实现的,欢迎留言和提交issue.

Comments ( 5 )

Sign in for post a comment

About

java small orm,使用jpa注解,不完全实现jpa规范-----最好用的orm框架之一 spread retract
Cancel

sorm

Contributors

All

Activities

load more
can not load any more
Java
1
https://gitee.com/parken/SormGit.git
git@gitee.com:parken/SormGit.git
parken
SormGit
sorm
master

Search