light-dao是一個輕量級的orm處理框架。
它的核心思想是盡可能簡單的使用原生sql。
它基于spring構建,可以和mybaties等其他數據處理框架配合使用。
一 由來
在我們的系統中,主要使用Mybaties作為ORM處理框架。但是Mybaties相對重量級,哪怕只是簡單使用,仍需要配置大量的xml。比如下面這樣的配置
countryList = sqlMapper.selectList("<script>" +
"select * from country " +
" <where>" +
" <if test=\"id != null\">" +
" id < #{id}" +
" </if>" +
" </where>" +
"</script>", country, Country.class);
在簡單sql的情況下還可以接受。當sql再復雜一些,比如:
<resultMap id="AssociationResultMap" type="com.test.mybatis.vo.MybatisOrder" >
<id column="ORDERID" property="orderid" jdbcType="DECIMAL" />
<result column="ORDERTYPE" property="ordertype" jdbcType="VARCHAR" />
<result column="ORDERDATE" property="orderdate" jdbcType="DATE" />
<association property="customer" column="CUSTOMERID"
resultMap="com.test.mybatis.mapper.MybatiscustomerMapper.BaseResultMap"/>
<collection property="itemList" column="ORDERID" javaType="ArrayList"
ofType="com.test.mybatis.vo.MybatisOrderItem"
resultMap="com.test.mybatis.mapper.MybatisOrderItemMapper.BaseResultMap"/>
</resultMap>
<select id="getOrderAssociation" parameterType="String" resultMap="AssociationResultMap">
SELECT *
FROM mybatisOrder ord LEFT JOIN mybatiscustomer customer ON ord.customerId = customer.ID
LEFT JOIN mybatisOrderItem item ON ord.orderid = item.orderid
WHERE ord.orderid = #{id}
</select>
作為一個習慣了使用原生Sql的開發者來說,實在是難以接受這種xml的設定,尤其時當一些純粹的數據查詢語句使用極為復雜的sql時,再對sql進行一次xml配置實在是有心無力。比如下面的這個sql:
select (IFNULL(payment, 0) - IFNULL(repayment, 0)) balance from
(select sum(amount) payment from payment_instruction_0000 where mifi_id in
(select distinct(t1.mifi_id) mifi_id from overdue_record t1, loan_contract_0000 t2
where t1.status = 1
and (t1.con_id = t2.id and t2.product_id in (76)))
and product_id in (76)) as pay,
(select sum(amount) repayment from repay_detail_instruction_0000 where repay_type in (1, 3, 7, 10) and mifi_id in
(select distinct(t1.mifi_id) mifi_id from overdue_record t1, loan_contract_0000 t2
where t1.status = 1
and (t1.con_id = t2.id and t2.product_id in (76)))
and product_id in (76)) as repay;
如果想改成xml配置......
真的做不到啊.......
在一次次的被mybaties的復雜配置搞的頭疼無奈之后;
在一次次的xml配置失敗,無限重新上線之后;
在一次次吐槽無力后
——
終于下定決心自己寫一套dao處理框架,這套框架中堅決不要再出現任何關于sql的xml。
而且要滿足下面幾個要求:
1 支持原始的sql使用
2 支持sql的變量替換
3 支持sql返回結果的映射
二 示例
開發了一月有余,目前差不多完成,也已經替換了線上的一些Mybaties部分。貼一下現在的示例代碼
@Dao(dbName = "my_db")
public interface InfoDao {
String TABLE_NAME = " info ";
String ALL_COLUMN = " id, information, user_id ";
String ALL_VALUES = " {param.id}, {param.information}, {param.userId} ";
@Data
class Info { //查詢結果的Bean,也也可以是Thrift生成的Bean
int id;
String information;
int userId;
}
@Execute("create table " + TABLE_NAME + " (id int, information varchar, user_id int)")
void create();
@Update("insert into " + TABLE_NAME + "(" + ALL_COLUMN + ")" + " values(" + ALL_VALUES + ")")
int insert(@SqlParam("param") Info info);
@Data
class UserInfo {
String information;
String name;
}
//跨表查詢示例
@Select("select information, name from info, user"
+ " where info.user_id = user.id")
List<UserInfo> selectUserInfo();
而針對上面代碼的數據庫配置只有如下的簡單幾行:
<bean id="myDbDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" lazy-init="false">
<property name="driverClassName" value="org.h2.Driver"></property>
<property name="url" value="jdbc:h2:mem:mifi_internal_account"></property>
<property name="username" value="sa"></property>
<property name="password" value=""></property>
</bean>
任你的sql再復雜,所有的配置也只是針對數據庫的訪問。
再貼一個復雜sql的使用情況:
@Select("select count(1) from (select t1.mifi_id, IFNULL(sum(amount), 0) payment from payment_instruction_0000 t1, "
+ " loan_contract_0000 t2 where (account_time >= {startTime} and account_time < {endTime})"
+ " and (t1.con_id = t2.id and t2.product_id in {productList})"
+ " group by mifi_id) as pay left join (select t1.mifi_id, sum(amount) repayment from repay_detail_instruction_0000 t1, "
+ " loan_contract_0000 t2 where (account_time >= {startTime} and account_time < {endTime})"
+ " and (t1.con_id = t2.id and t2.product_id in ({productList}))"
+ " and (t1.repay_type in (1, 3, 7, 10))"
+ " group by mifi_id) as repay on pay.mifi_id = repay.mifi_id where payment - IFNULL(repayment, 0) > 0")
long getBalanceNum(@SqlParam("startTime") long startTime, @SqlParam("endTime") long endTime, @StringParam("productList") String productList)
基本就像把原生sql直接傳進去一樣。
三 源碼講解:
源碼基本上分為三層
1 spring層,用于向spring的BeanFactory注冊用戶自定義的Dao接口
2 executor層,將Dao接口中的每一個方法組裝成一個一個的executor執行體
3 數據庫層,執行實際的sql
結構圖如下:
具體實現部分請參考源碼,注釋比較豐富,實現也相對比較直觀。
四 使用方式
1 聲明一個Dao接口類
@Dao(dbName = "my_db") //必須使用@Dao注解 必須指明數據庫的名字(dbName)
public interface InfoDao //接口名必須以Dao結尾(方便被Resolver發現)
2 將sql語句定義為方法
@Select("select id, name from " + TABLE_NAME + " where id = {id}")
User select(@SqlParam("id") int id);
a sql語句的注解必須為@Select, @Update, @Execute三種之一,一般來說,select表示查詢語句,update表示有寫入的語句,比如insert和update,execute表示ddl語句,比如create table這種
b 在sql語句中,請將參數使用{}來標記。一般來說,可以標記正常的命名參數,比如{id},還可以標記bean的屬性參數,比如{user.name}
c 函數參數也有兩種標記方式,@StringParam和@SqlParam,被@StringParam標記的參數將直接替換sql中同名的參數,比如"select {name} from user" @StringParma String name("littlersmall"),sql語句將被替換為"select littlersmall from user"。通常用于一些可變字符串的直接替換。
被@SqlParam標記的參數有兩種形式,普通的命名參數和bean的屬性參數,而且參數可以是任意類型,比如:
{id} @SqlParam int id;
{user.name} @SqlParam User user(無需再寫user.name);
該方式主要用于sql的參數替換
d 返回值通常有幾種:
(1) select類型,可以返回基本類型,比如int, long等,還可以返回用戶自定義的Bean類型,比如User比如Thrift生成的bean,還可以返回多條結果,使用一個List,比如List<User>, List<String>
(2) Update類型,只能返回int,表示插入或修改的行數
(3) Execute類型無返回值
3 使用Dao接口
@Service //一定是被spring管理的類
public class UserDaoExample {
@Autowired UserDao userDao; //直接Autowired就好
...
}
4 數據庫配置,請寫在applicationContext.xml中,例如
<bean id="MyDbDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" lazy-init="false">
<property name="driverClassName" value="org.h2.Driver"></property>
<property name="url" value="jdbc:h2:mem:mifi_internal_account"></property>
<property name="username" value="sa"></property>
<property name="password" value=""></property>
</bean>
a 請使用數據庫名作為beanId的前綴,比如本例中,數據庫的名字為my_db,使用大寫的駝峰命名方式MyDb
b beanId結尾部分請使用DataSource
五 源碼地址
github地址
路過的幫忙點個星星啊,謝謝_ 。
請直接使用mvn package獲得項目的jar包
或者通過mvn deploy的方式將其構建到自己的中央庫中
六 參考
項目參考了大量的網上資料,感謝baidu,google,bing,soso
參考了部分mybaties實現,部分spring實現,以及paoding-rose框架
七 總結
1 有一個大致的方向之后就應該馬上動手,越拖延越沒有動力
2 java的反射確實很強大
3 每個類,每個函數盡量只做一件事情
4 反復的重構很有必要,而且也很有收獲
5 再復雜的代碼,也是由最簡單的東西一步步進化而來