# DAF4J **Repository Path**: taoyy/DAF4J ## Basic Information - **Project Name**: DAF4J - **Description**: Data Access Facade For Java,更简单的持久层结构,并为数据访问提供统一接口 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2015-11-11 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #Data Access Facade for Java (DAF4J) 这是一个针对数据访问的统一接口,提供简便的,带类型检查的数据访问 >运行于 Java 1.6 以及更高版本 >引入`daf4j-api.jar`和您需要的`DataAccess`实现即可 >DAF4j针对Scala提供了的简化操作 `update.md`中查看更新日志 ###使用 方式1: 在`git@osc`上下载附件并引入包,以及根据说明引入依赖项(`daf4j-api.jar`本身不需要引入依赖,但`DataAccess`实现会需要,后文有详细说明) 方式2: 使用maven引入 ```xml net.cassite daf4j-api 0.0.3-RELEASE ``` ###使用预览 **Java** | Stream风格 ```java query .from(user) .stream() .filter(user.age.$gt(18).and(user.name.$ne("cass"))) .filter(role.name.$eq("admin")) .sorted(user.id.desc()) .limit(10) .list(); // 查询年龄大于18,姓名不为'cass',角色等于'admin'的用户,根据用户id降序排列,并取前10条记录 ``` **Scala** | SQL风格 ```scala query from user where user.age > 18 & user.name <> "cass" & role.name === "admin" param (orderBy(user.id.desc) top 10) list // 功能与上相同 ``` ###开发这个类库的原因 DAF4J用于简化数据访问。 三层架构中数据层的结构会很大程度上依赖于业务逻辑。很多时候业务层仅仅做一些数据校验和数据类型的转换,剩下就是直接的调用数据层,而数据层有时候也会针对业务层方法写一些“专用”方法。 我认为数据层之所以依赖于业务的根源,是查询/修改**条件**无法完整的从业务中分离出去。 即使是类似`Spring Data JPA`或者`JPA Criteria`,对于条件的处理也略显笨重(`小于(字段, 值)`而不是`字段.小于(值)`,而且代码量略高)。 我想,既然`JPA`支持从方法注入值,那么为何不转变一下思路,把字段替换成封装好,支持各种条件和表达式的类型,而方法签名不变。这样既可以使用已有框架,又可以更轻松的调用数据。 设计了一番后便开始了开发。 ##使用方式 >此处给出一个前瞻,详细使用方式随后给出 >在Wiki页提供一篇了教学,在一个简单的RBAC的模型的情景下使用DAF4J 使用需要引入`daf4j-api.jar` 本例以`daf4j-ds-jpa.jar`中的`JPQLDataAccess`实现为例 我们会书写一个实体类,现在把实现方式作微小的调整: 我针对`IDEA`写了一个getter和setter的生成器,来完成DAF4J的JavaBean生成,生成代码见仓库根目录`GetterGeneratorForIDEA.txt`和`SetterGeneratorForIDEA.txt` ```java @Entity class User{ public final XInt id = new XInt(this); public final XString name = new XString(this); public final XInt age = new XInt(this); @Id public Integer getId(){ return id.get(); } public String getName(){ return name.get(); } public Integer getAge(){ return age.get(); } public void setId(Integer id){ DataUtils.set(this.id, id); } public void setName(String name){ DataUtils.set(this.name, name); } public void setAge(Integer age){ DataUtils.set(this.age, age); } } ``` 为了使用JPA的功能,还需要获取一个`EntityManager`,然后用它初始化`JPQLDataAccess`和`Query` ```java Query query=new Query(new JPQLDataAccess(entityManager)); ``` 接着就可以开始使用了: ```java User user=new User(); // 列出所有年龄大于18岁的用户 query .from(user) .where(user.age.$gt(18)) .list(); // 以List>的形式列出用户id和用户名 // Map中的键包括 User.id 和 user_name // 结果根据年龄降序排序 query .from(user) .where(user.age.$gt(18)) .param( new QueryParameter().orderBy(user.age.desc()) ) .select( new Focus() .focus(user.id) // 别名为类型简称.字段名 .focus(user.name, "user_name") ); // 计算所有用户年龄平均值 query .from(user) .where(null) .avg(user.age); // 将所有用户的年龄+1 query .from(user) .where(null) .update( user.age.as(user.age.add(1)) ); // 删除所有用户 query .from(user) .where(null) .remove(); ``` 上述查询风格是sql风格,有时候并不操作关系型数据库,或者不喜欢类sql的语法,所以此处还提供了stream风格的查询语句 ```java // 列出所有年龄大于18岁的用户 query .from(user).stream() .filter(user.age.$gt(18)) .list(); // 以List>的形式列出用户id和用户名 // Map中的键包括 User.id 和 user_name // 结果根据年龄降序排序 query .from(user).stream() .filter(user.age.$gt(18)) .sorted(user.age.desc()) .map( new Focus() .focus(user.id) .focus(user.name, "user_name") ) .list(); // 计算所有用户年龄平均值 query .from(user).stream() .mapToInt(user.age) .average(); ``` 以上查询都是带类型检查的。 ##DAF4J 详细使用说明 首先引入`daf4j-api.jar`,它运行于 java 1.6 或以上,无需任何依赖项。 您也可以使用Maven引用它。 ```xml net.cassite daf4j-api 0.0.3-RELEASE ``` 对于Scala使用者,您可能还需要`daf4j-scala-api.jar`来使用针对Scala作出的优化。或者使用Maven引入(过段时间会换成SBT并发布到中央仓库,抱歉,最近还在熟悉SBT环境) ```xml releases http://maven.cassite.net/content/repositories/releases net.cassite daf4j-scala-api 0.0.1-RELEASE ``` 然后选取合适的DataAccess或者实现一个,并引入必需的依赖。 >后面的说明中包含了已有DataAccess实现的使用说明 接着定义你的实体类。注意,实体类字段最好使用`public final`修饰,字段类型写为如下三种 * Data<> * DataComparable<> * DataIterable<> 或者使用`net.cassite.daf4j.types`中提供的封装好的类型(XType),比如 * XInt * XLong * XDouble * XString * XDate * XList * XSet 实际上它们分别各自继承于上面三种泛型类型的其中之一。 `Data<>`提供最基本的操作 `DataComparable<>`提供比较操作 `DataIterable<>`提供一些聚合相关操作 构造时将实体引用`this`传入,例如 ```java public final XInt id = new XInt(this); // 等同于 // public final DataComparable id=new DataComparable(this); ``` getters需要这么写 ```java String getName(){ return this.name.get(); } ``` setters需要这么写 ```java void setName(String name){ DataUtils.set(this.name, name); } ``` 之所以要用到DataUtils来set是为了防止在外部错误的调用字段的.set()方法。 ####IDEA Getter & Setter 生成器 此处提供了一个针对IDEA的Getter & Setter生成器,来完成这种特殊的getter/setter生成,生成代码见仓库根目录`GetterGeneratorForIDEA.txt`和`SetterGeneratorForIDEA.txt` **接下来就可以开始正常使用了** 使用一个DataAccess来初始化Query ```java Query query = new Query(dataAccess); ``` `Query`对象拥有如下方法: * [En] From[En] from(En entity) // 开启一个标准查询 * void save(Object... entities) // 保存/持久化指定实体 * [En] En find(Class[En] entityClass, Object pkValue) // 根据主键/标识查询 其中`from(...)`方法返回一个`From`对象,它将继续接受查询参数 `From`对象拥有如下方法: * PreResult[En] where(Where whereClause) // 接收条件参数 * QueryStream[En] stream() // 切换为stream风格查询 `where(...)`方法接收条件参数,并继续sql风格的查询。返回的PreResult可以进行查询/修改/删除操作。 `stream()`方法开启stream风格查询,将以`filter`方法代替`where`方法,并且可以连续调用多个filter方法,它们将以And连接。要注意的是,stream风格**只能够进行查询**,*不能* 进行修改。这考虑到stream是函数式的,通常副作用在函数式中尽量少出现。 stream风格可以让许多操作变得更美观。 ###SQL风格 ```java query.from(实体).where(条件) ``` 将返回PreResult对象。它拥有如下方法: * param(QueryParameter) * // 设置查询参数,在查询时会用到。QueryParameter提供orderBy,top,limit方法 * list() // 返回包含实体的List * first() // 返回符合条件的第一个实体,没找到则返回空 * selectAll() // 返回包含所有非聚合字段的List[Map],并且键的格式为 `类简称.字段名` * select(Focus) * // 根据提供的Focus对象给出查询结果。Focus可接收任何Selectable类型(IData,IExpression),还可以指定Map中对应键的名称 * saveAs(样本实体) * // 将满足条件实体的可赋值字段全部修改为指定实体提供的非空值(若为空则不修改) * set(UpdateEntry...) // 执行更新 * remvoe() // 删除操作 * count(DataComparable) // 执行计数 * sum(XType) // 执行求和 * avg(XType) // 执行求平均值 * max(XType) // 取最大值 * min(XType) // 取最小值 >其中XType为`net.cassite.daf4j.types`包中的XInt,XLong,XDouble,分别会返回不同的基本数据类型 >另外还对一些没有使用XType,而仅仅使用DataComparable的实体提供类似sumLong,sumDouble之类的方法。 ###Stream风格 ```java query.from(实体).stream() ``` 将返回`QueryStream`对象。 这里总共有三类Stream, 1. QueryStream 2. QueryProjectionStream 3. QueryIntStream, QueryLongStream, QueryDoubleStream 它们有共同的基类`QueryStreamBase`,提供如下方法: * fitler(AndOr) // 筛选条件。可以多次调用 * sorted() * // 对于实现了SortedEntity接口的实体可以直接排序,否则抛出异常 * sorted(OrderBase...) // 根据指定参数进行排序 * limit(int) // 取前几条 * limit(int,int) // 取起始到结尾条数的记录 `QueryStream`可以开启另外两类Stream,使用如下方法: * mapAll() // 功能类似selectAll,并开启QueryProjectionStream * map(Focus) // 功能类似select,并开启QueryProjectionStream * mapToInt(DataComparable) // 选取字段并开启QueryIntStream * mapToLong(DataComparable) // 选取字段并开启QueryLongStream * mapToDouble(DataComparable) // 选取字段并开启QueryDoubleStream 它还有自己特有的方法: * count() // 计数 * list() // 列出实体 对于`QueryProjectionStream`,可以调用`list()`列出List[Map],和select返回值一致 对于第三类Stream,它们拥有sum,max,min,average的无参方法。 >以上stream均实现了Iterable接口,若您正在使用Java8则可以继续调用stream()进行数据分析。 ##已有的DataAccess实现 现在仅仅开发了针对`JPA`标准的DataAccess实现。测试环境中使用Hibernate-EntityManager-4.0,已在`HSQLDB`和`MySQL`中测试通过。 >如果你下载了源码,则可以直接运行测试用例,`HSQLDB`将在内存运行 依赖于 * `daf4j-api:0.0.3-RELEASE` * `slf4j-api:1.7.12` * `hibernate-jpa-2.0-api:1.0.1.Final` 当然,日志输出还需要额外的日志系统,比如slf4j-simple/log4j+slf4j-log4j等,JPA实现也需要引入依赖。 您可以自行添加依赖或者使用maven ```xml net.cassite daf4j-ds-jpa 0.0.3-RELEASE ``` ###JPQLDataAccess `JPA`是 JavaEE 体系中的一部分,是数据持久化的标准。所以对其进行了实现。JPA提供了`JPQL`和`Criteria`两种查询方式,但是Criteria方式限制很大,所以采用JPQL查询方式作实现。 关系型数据库可以进行join,group by,having,然而daf4j-api中并没有这些元素。这起源于我曾经[针对SQL简化的思考](http://blog.cassite.net/杂谈/有关SQL的简化的思考)。 1. SQL中,若出现聚合函数,那么出现在select子句中却没有出现在聚合函数中的字段必需出现在group by子句中。 2. 若聚合函数作为条件的一部分,那么必需放在having子句中。 也就是说,大部分情况下,group by和having可以推导出来。 而使用JPA时,join关系已经在实体中定义好了。即使写JPQL时也只会这么写 ```sql select u from User u join u.roles r ... ``` 完全可以自动生成。 不过此处还有一些使用规则。 例如User(用户)和Role(角色),如果需要查询用户,也需要以角色为依据。比如说:查询出所有角色为`admin`的用户,那么你必需这么写。 ```java User user=new User(); Role role=new Role(); user.getRoles().add(role); query .from(user) .stream() .filter(role.name.$eq("admin")) .list(); ``` 换句话说,你需要指定join的字段。 如果是多对一关系,则需要通过setter填入。 ##Scala特殊用法 这里不作过多叙述,如下代码一目了然: ```scala import entity._ query from entity where age > 18 & id <> 1 param (orderBy(name.desc) top 1) list() query from entity where age > 18 & id <> 1 param (orderBy(name.desc) top 1) select id ~(name, "a") query from entity stream() filter age > 18 & id <> 1 sorted name.desc limit 5 list() query from entity stream() filter age > 18 & id <> 1 sorted name.desc limit 5 map id ~(name, "a") list() ```