dexcoder-dal通用數(shù)據(jù)庫訪問層
核心組件dexcoder-dal使用說明
如果你不喜歡用Hibernate、Mybaits這類ORM框架,喜歡JdbcTemplate或DbUtils,那么可以試試這個封裝的通用dal,這可能是目前封裝的最方便易用的通用dal層了。
dexcoder-dal的一些特性:
一個dao即可以搞定所有的實體類,不必再一個個建立跟實體對應(yīng)的繼承于類似BaseDao這類“通用dao”了。
各類方法參數(shù)除了Entity外,支持更強大的Criteria方式。
sql的where條件支持一些復(fù)雜的條件,如等于、不等于、or、in、not in甚至是執(zhí)行函數(shù)。
允許在查詢時指定使用哪個字段進行排序,可以指定多個進行組合升降序自由排序。
支持在查詢時指定返回字段的白名單和黑名單,可以指定只返回某些字段或不返回某些字段。
select查詢時支持函數(shù),count()、max()、to_char()、甚至是distinct,理論上都可以支持。
方便強大的分頁功能,無須額外操作,二三行代碼搞定分頁,自動判斷數(shù)據(jù)庫,無須指定。
可以使用{}和[]完成一些特殊的操作,{}中的代碼將原生執(zhí)行,[]中的代碼會進行命名轉(zhuǎn)換,一般fieldName轉(zhuǎn)columnName。
支持執(zhí)行自定義sql。
支持使用類似mybatis的方式執(zhí)行自定義sql。
支持讀寫分離和動態(tài)數(shù)據(jù)源。
該通用dao是在使用過程中,針對常規(guī)的泛型dao經(jīng)常遇到的一些不便問題進行了改進。命名上遵循了約定優(yōu)于配置的原則,典型約定如下:
表名約定USER_INFO表實體類名為UserInfo。
字段名約定USER_NAME實體類中屬性名為userName。
主鍵名約定USER_INFO表主鍵名為USER_INFO_ID,同理實體類中屬性名為userInfoId。
Oracle序列名約定USER_INFO表對應(yīng)的主鍵序列名為SEQ_USER_INFO
當(dāng)然,這些你可以在擴展中改變它,但不建議這么做,這本身就是一個良好的規(guī)范。
要在項目中使用通用dao十分簡單,目前已上maven中央庫,直接在pom.xml中添加依賴:
<dependency> <groupId>com.dexcoder</groupId> <artifactId>dexcoder-dal-spring</artifactId> <version>2.2.0-beta1</version> </dependency>
然后在spring的配置文件中聲明如下bean:
<bean id="jdbcDao" class="com.dexcoder.dal.spring.JdbcDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <!--需要分頁時聲明--> <bean id="pageControl" class="com.dexcoder.dal.spring.page.PageControl"></bean>
接下來就可以注入到您的Service或者其它類中使用了。
下面是一些常用的方法示例,這里的Entity對象為User,對于任何的Entity都是一樣的.
先來看一下User對象及它繼承的Pageable
public class User extends Pageable {
private Long userId;
private String loginName;
private String password;
private Integer userAge;
private String userType;
private String email;
private Date gmtCreate;
//......
}
Pageable對象,用來保存頁碼、每頁條數(shù)信息以支持分頁
public class Pageable implements Serializable {
/** 每頁顯示條數(shù) */
protected int itemsPerPage = 20;
/** 當(dāng)前頁碼 */
protected int curPage = 1;
//......
}
都是普通的JavaBean對象,下面來看看如何進行具體的增刪改查,每種操作都演示了Entity和Criteria兩種方式。
insert操作
public void insert() {
User user = new User();
user.setLoginName("selfly_a");
user.setPassword("123456");
user.setEmail("[email protected]");
user.setUserAge(18);
user.setUserType("1");
user.setGmtCreate(new Date());
Long id = jdbcDao.insert(user);
System.out.println("insert:" + id);
}
public void insert2() {
Criteria criteria = Criteria.insert(User.class).into("loginName", "selfly_b").into("password", "12345678")
.into("email", "[email protected]").into("userAge", 22).into("userType", "2").into("gmtCreate", new Date());
Long id = jdbcDao.insert(criteria);
System.out.println("insert:" + id);
}
save操作,和insert的區(qū)別在于不處理主鍵,由調(diào)用者指定
public void save() {
User user = new User();
user.setUserId(-1L);
user.setLoginName("selfly-1");
user.setPassword("123456");
user.setEmail("[email protected]");
user.setUserAge(18);
user.setUserType("1");
user.setGmtCreate(new Date());
jdbcDao.save(user);
}
public void save2() {
Criteria criteria = Criteria.insert(User.class).into("userId", -2L).into("loginName", "selfly-2")
.into("password", "12345678").into("email", "[email protected]").into("userAge", 22).into("userType", "2")
.into("gmtCreate", new Date());
jdbcDao.save(criteria);
}
update操作
public void update() {
User user = new User();
user.setUserId(57L);
user.setPassword("abcdef");
user.setGmtModify(new Date());
jdbcDao.update(user);
}
public void update2() {
Criteria criteria = Criteria.update(User.class).set("password", "update222")
.where("userId", new Object[] { 56L, -1L, -2L });
jdbcDao.update(criteria);
}
get操作
public void get1() {
User u = jdbcDao.get(User.class, 63L);
Assert.assertNotNull(u);
System.out.println(u.getUserId() + " " + u.getLoginName() + " " + u.getUserType());
}
public void get2() {
//criteria,主要用來指定字段白名單、黑名單等
Criteria criteria = Criteria.select(User.class).include("loginName");
User u = jdbcDao.get(criteria, 73L);
Assert.assertNotNull(u);
System.out.println(u.getUserId() + " " + u.getLoginName() + " " + u.getUserType());
}
delete操作
public void delete() {
//會把不為空的屬性做為where條件
User u = new User();
u.setLoginName("selfly-1");
u.setUserType("1");
jdbcDao.delete(u);
}
public void delete2() {
//where條件使用了or
Criteria criteria = Criteria.delete(User.class).where("loginName", new Object[] { "liyd2" })
.or("userAge", new Object[]{64});
jdbcDao.delete(criteria);
}
public void delete3() {
//根據(jù)主鍵
jdbcDao.delete(User.class, 57L);
}
列表查詢操作
public void queryList() {
//所有結(jié)果
List<User> users = jdbcDao.queryList(User.class);
}
public void queryList1() {
//以不為空的屬性作為查詢條件
User u = new User();
u.setUserType("1");
List<User> users = jdbcDao.queryList(u);
}
public void queryList2() {
//Criteria方式
Criteria criteria = Criteria.select(User.class).exclude("userId")
.where("loginName", new Object[]{"liyd"});
List<User> users = jdbcDao.queryList(criteria);
}
public void queryList3() {
//使用了like,可以換成!=、in、not in等
Criteria criteria = Criteria.select(User.class).where("loginName", "like",
new Object[] { "%liyd%" });
user.setUserAge(16);
//這里entity跟criteria方式混合使用了,建議少用
List<User> users = jdbcDao.queryList(user, criteria.include("userId"));
}
count記錄數(shù)查詢,除了返回值不一樣外,其它和列表查詢一致
public void queryCount() {
user.setUserName("liyd");
int count = jdbcDao.queryCount(user);
}
public void queryCount2() {
Criteria criteria = Criteria.select(User.class).where("loginName", new Object[] { "liyd" })
.or("userAge", new Object[]{27});
int count = jdbcDao.queryCount(criteria);
}
查詢單個結(jié)果
public void querySingleResult() {
user = jdbcDao.querySingleResult(user);
}
public void querySingleResult2() {
Criteria criteria = Criteria.select(User.class).where("loginName", new Object[] { "liyd" })
.and("userId", new Object[]{23L});
User u = jdbcDao.querySingleResult(criteria);
}
指定字段白名單,在任何查詢方法中都可以使用
public void get(){
//將只返回userName
Criteria criteria = Criteria.select(User.class).include("loginName");
User u = jdbcDao.get(criteria, 23L);
}
指定字段黑名單,在任何查詢方法中都可以使用
public void get4(){
//將不返回loginName
Criteria criteria = Criteria.select(User.class).exclude("loginName");
User u = jdbcDao.get(criteria, 23L);
}
指定排序
public void queryList() {
//指定多個排序字段,asc、desc
Criteria criteria = Criteria.select(User.class).exclude("userId")
.where("loginName", new Object[]{"liyd"}).asc("userId").desc("userAge");
List<User> users = jdbcDao.queryList(criteria);
}
分頁
public void queryList1() {
//進行分頁
PageControl.performPage(user);
//分頁后該方法即返回null,由PageControl中獲取
jdbcDao.queryList(user);
Pager pager = PageControl.getPager();
//列表
List<User> users = pager.getList(User.class);
//總記錄數(shù)
int itemsTotal = pager.getItemsTotal();
}
public void queryList2() {
//直接傳入頁碼和每頁條數(shù)
PageControl.performPage(1, 10);
//使用Criteria方式,并指定排序字段方式為asc
Criteria criteria = Criteria.select(User.class).include("loginName", "userId")
.where("loginName", new Object[]{"liyd"}).asc("userId");
jdbcDao.queryList(criteria);
Pager pager = PageControl.getPager();
}
不同的屬性在括號內(nèi)or的情況:
Criteria criteria = Criteria.select(User.class)
.where("userType", new Object[] { "1" }).begin()
.and("loginName", new Object[] { "selfly" })
.or("email", new Object[] { "[email protected]" }).end()
.and("password", new Object[] { "123456" });
User user = jdbcDao.querySingleResult(criteria);
執(zhí)行函數(shù)
//max()
Criteria criteria = Criteria.select(User.class).addSelectFunc("max([userId])");
Long userId = jdbcDao.queryForObject(criteria);
//count()
Criteria criteria = Criteria.select(User.class).addSelectFunc("count(*)");
Long count = jdbcDao.queryForObject(criteria);
//distinct
Criteria criteria = Criteria.select(User.class).addSelectFunc("distinct [loginName]");
List<Map<String, Object>> mapList = jdbcDao.queryForList(criteria);
默認(rèn)情況下,addSelectFunc方法返回結(jié)果和表字段互斥,并且沒有排序,如果需要和表其它字段一起返回并使用排序,可以使用如下代碼:
Criteria criteria = Criteria.select(User.class).addSelectFunc("DATE_FORMAT(gmt_create,'%Y-%m-%d %h:%i:%s') date",false,true);
List<Map<String, Object>> mapList = jdbcDao.queryForList(criteria);
這是在select中執(zhí)行函數(shù),那怎么在update和where條件中執(zhí)行函數(shù)呢?前面提到的{}和[]就可以起到作用了。
看下面代碼:
Criteria criteria = Criteria.update(User.class).set("[userAge]", "[userAge]+1")
.where("userId", new Object[] { 56L });
jdbcDao.update(criteria);
以上代碼將執(zhí)行sql:UPDATE USER SET USER_AGE = USER_AGE+1 WHERE USER_ID = ?,[]中的fieldName被轉(zhuǎn)換成了columnName,
也可以使用{}直接寫columnName,因為在{}中的內(nèi)容都是不做任何操作原生執(zhí)行的,下面代碼效果是一樣的:
Criteria criteria = Criteria.update(User.class).set("{USER_AGE}", "{USER_AGE + 1}")
.where("userId", new Object[] { 56L });
jdbcDao.update(criteria);
同理,在where中也可以使用該方式來執(zhí)行函數(shù):
Criteria criteria = Criteria.select(User.class).where("[gmtCreate]", ">",
new Object[] { "str_to_date('2015-10-1','%Y-%m-%d')" });
List<User> userList = jdbcDao.queryList(criteria);
表別名支持
有些時候,就算單表操作也必須用到表別名,例如oracle中的xmltype類型。可以在Criteria中設(shè)置表別名:
Criteria criteria = Criteria.select(Table.class).tableAlias("t").addSelectFunc("[xmlFile].getclobval() xmlFile")
.where("tableId", new Object[]{10000002L});
Object obj = jdbcDao.queryForObject(criteria);
//對應(yīng)的sql
select t.XML_FILE.getclobval() xmlFile from TABLE t where t.TABLE_ID = ?
執(zhí)行自定義sql
在實際的應(yīng)用中,一些復(fù)雜的查詢?nèi)缏?lián)表查詢、子查詢等是省不了的。鑒于這類sql的復(fù)雜性和所需要的各類優(yōu)化,通用dao并沒有直接封裝而是提供了執(zhí)行自定義sql的接口。
執(zhí)行自定義sql支持兩種方式:直接傳sql執(zhí)行和mybatis方式執(zhí)行。
直接傳sql執(zhí)行
該方式可能會讓除了dao層之外的業(yè)務(wù)層出現(xiàn)sql代碼,因此是不推薦的,它適合一些不在項目中的情況。
何為不在項目中的情況?例如做一個開發(fā)自用的小工具,臨時處理一批業(yè)務(wù)數(shù)據(jù)等這類后期不需要維護的代碼。
要執(zhí)行自定義sql首先需要在jdbcDao中注入sqlFactory,這里使用SimpleSqlFactory:
<bean id="jdbcDao" class="com.dexcoder.dal.spring.JdbcDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> <property name="sqlFactory" ref="sqlFactory"/> </bean> <bean id="sqlFactory" class="com.dexcoder.dal.SimpleSqlFactory"> </bean>
然后就可以直接傳入sql執(zhí)行了:
List<Map<String, Object>> list = jdbcDao.queryForSql("select * from USER where login_name = ?",
new Object[] { "selfly_a99" });
這個實現(xiàn)比較簡單,參數(shù)Object數(shù)組中不支持復(fù)雜的自定義對象。
mybatis方式執(zhí)行
采用了插件式實現(xiàn),使用該方式首先添加依賴:
<dependency> <groupId>com.dexcoder</groupId> <artifactId>dexcoder-dal-batis</artifactId> <version>2.2.0-beta1</version> </dependency>
之后同樣注入sqlFactory,把上面的SimpleSqlFactory替換成BatisSqlFactoryBean:
<bean id="jdbcDao" class="com.dexcoder.dal.spring.JdbcDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> <property name="sqlFactory" ref="sqlFactory"/> </bean> <bean id="sqlFactory" class="com.dexcoder.dal.batis.BatisSqlFactoryBean"> <property name="sqlLocation" value="user-sql.xml"/> </bean>
BatisSqlFactoryBean有一個sqlLocation屬性,指定自定義的sql文件,因為使用了spring的解析方式,所以可以和指定spring配置文件時一樣使用各類通配符。
user-sql.xml是一個和mybatis的mapper類似的xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//dexcoder.com//DTD Mapper 2.0//EN"
"http://www.dexcoder.com/dtd/batis-mapper.dtd">
<mapper namespace="User">
<sql id="columns">
user_id,login_name,password,user_age,user_type
</sql>
<select id="getUser">
select
<include refid="columns"/>
from user
<where>
<if test="params[0] != null">
user_type = #{params[0].userType}
</if>
<if test="params[1] != null">
and login_name in
<foreach collection="params[1]" index="index" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
</where>
</select>
</mapper>
然后使用代碼調(diào)用:
User user = new User();
user.setUserType("1");
Object[] names = new Object[] { "selfly_a93", "selfly_a94", "selfly_a95" };
List<Map<String, Object>> mapList = jdbcDao.queryForSql("User.getUser", "params", new Object[] { user, names });
for (Map<String, Object> map : mapList) {
System.out.println(map.get("userId"));
System.out.println(map.get("loginName"));
}
我們調(diào)用queryForSql方法時傳入了三個參數(shù):
User.getUser 具體的sql全id,namespace+id。
params 自定義sql中訪問參數(shù)的key,如果不傳入默認(rèn)為item。
Object[] sql中用到的參數(shù)。訪問具體參數(shù)時可以使用item[0],item[1]對應(yīng)里面相應(yīng)的元素,支持復(fù)雜對象。
可以看到這里支持復(fù)雜參數(shù),第一個是Userbean對象,第二個是Object數(shù)組,至于獲取方式可以看上面的xml代碼。
除了傳入的參數(shù)為Object數(shù)組并使用item[0]這種方式訪問相應(yīng)的元素外,其它的和mybatis可以說是一樣的,mybatis支持的動態(tài)sql方式這里也可以支持,因為他本身就是來源于mybatis。
另外返回結(jié)果中map的key做了LOGIN_NAME到駱駝命名法loginName的轉(zhuǎn)換。
一些說明
BatisSqlFactory方式由分析了mybatis源碼后,提取使用了大量mybatis的代碼。
JdbcDao在聲明時可以根據(jù)需要注入其它幾個參數(shù):
<bean id="jdbcDao" class="com.dexcoder.dal.spring.JdbcDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> <property name="sqlFactory" ref="..."/> <property name="nameHandler" ref="..."/> <property name="rowMapperClass" value="..."/> <property name="dialect" value="..."/> </bean>
nameHandler 默認(rèn)使用DefaultNameHandler,即遵守上面的約定優(yōu)于配置,如果需要自定義可以實現(xiàn)該接口。
sqlFactory 執(zhí)行自定義sql時注入相應(yīng)的sqlFactory。
rowMapperClass 默認(rèn)使用了spring的BeanPropertyRowMapper.newInstance(clazz),需要自定義可以自行實現(xiàn),標(biāo)準(zhǔn)spring的RowMapper實現(xiàn)即可。
dialect 數(shù)據(jù)庫方言,為空會自動判斷,一般不需要注入。
