1 Star 0 Fork 7

luchao111 / sorm

forked from Show / sorm 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

Small ORM

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

  • 开发效率
    • 全面拥抱JPA注解,并通过对注解的扩展和JPA实体类的增强,及其方便的完成单表增删改查功能.
    • 数据模型支持Pojo,Map/List
    • 支持自动建表功能.创建完成JPA实体类,运行即可创建数据库表
    • 支持级联功能,但不实现延迟加载功能(通过手动调用,才能加载级联对象,此处主要降低jpa级联实现的复杂度).
    • 支持Map格式的数据对象返回(由于不区分字段大小写,要求数据库设计对字段大小写不敏感).
    • 支持乐观锁功能.
    • 支持SQL查询转换为实体对象功能
    • 支持类似mybatis的resultMap,但无需编写xml映射,实体类只需使用@Column注解和数据库字段对应即可,对于sql语句返回值映射到对象有子类的情况,使用@SmallResults注解标记即可实现主类、子类的组装.
    • 支持实体类中的类似mybatis Example查询
    • 集成支持querydsl,jooq用法,能降低80%~90%的sql硬编码.极大提高系统的可维护性.
    • SQL 模板基于enjoy实现,更容易写和调试,以及扩展
    • 可以针对单个表(或者视图)代码生成pojo类
  • 维护性
    • 使用类型安全的查询,当修改数据库表字段时,可以通过编译提示方便修改类字段.避免mybatis修改数据库字段带来的调试风险.
    • 无缓存功能,避免hibernate,mybatis带来的缓存一致性问题.
    • SQL 以enjoy模板管理,支持自定义文件位置,方便程序开发和数据库SQL调试。
    • 可以自动将sql文件映射为dao接口类
  • 其他
    • 基于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,sqllite.
    • 支持脱离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>1.0.10</version>
</dependency>

配置maven插件

<plugin>
    <groupId>com.github.atshow</groupId>
    <artifactId>sorm</artifactId>
    <version>1.0.10</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.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的问题

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

额外说明

  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的生效

实体类增强.

在上面的例子中,还可以看到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);

三、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类型参数传入:

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

模板支持跨数据库

dbContext.setSqlTemplateIdUseDialect(true);//打开开关,该开关优先级高于@SqlResource
dt.selectListTemplate(User.class, "queryUserByName", params)// 对于mysql数据库框架会查找模板id为 mysql.queryUserByName 的模板内容

模板指令

  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

五、整合QueryDSL和jOOQ

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

  1. 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);
  1. Jooq 主要支持开源的jooq版本,对于商业版本,先在maven配置中排除sorm自带的开源版本,再引入商业版本; 并在生成语句时,使用DSL.using(Dialect)方法设置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做了集成.

  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 整合. 以下属性允许在application.properties中配置.
smallorm.packages=db.domain                         #指定扫描若干包,如果有多个请使用,分隔
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模板类型(默认)

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

八、其他功能

  1. 映射的高级用法 对类JPA实体类中的字段为集合,Map类型时,支持将集合,Map转为JSON字符串存入数据库字段中,查询时,能将json字段转换为对象中集合字段. 以下三种写法效果一致.
@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实例即可.

FAQ

  • 模板里没有not in指令
    答:可以使用 not #in(..) 合并使用
  • JAP实体类如何更新主键
    答:通过在对象中设置新主键值,并使用useQuery设置老的主键即可.
User u = new User();
u.setId(23);
u.useQuery().createCriteria().eq(User.Field.id, 1);
dt.update(u);
  • 使用JPA实体类构造方法无法保存或修改数据库记录
    答:必须使用set方法,使用构造方法无法设置对象的变更信息.
  • 是否支持多主键
    答:支持,多个主键使用@Id标注即可,免去了JPA复合主键定义的麻烦.
  • 框架是否有缓存功能
    答:未提供缓存功能,可以在业务层通过spring提供的缓存功能实现.此处考虑到添加缓存后,会带来更多的优化调试风险.
  • 框架是否有拦截器功能
    答:无,如果有什么更便捷的使用方法,而框架未实现的,欢迎留言和提交issue.
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 ShowS Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

java small orm,使用jpa注解,不完全实现jpa规范-----最好用的orm框架之一 展开 收起
Java
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/luchao111/SormGit.git
git@gitee.com:luchao111/SormGit.git
luchao111
SormGit
sorm
master

搜索帮助