簡(jiǎn)介
fastmybatis是一個(gè)mybatis開發(fā)框架,其宗旨為:簡(jiǎn)單、快速、有效。
- 零配置快速上手
- 無需編寫xml文件即可完成CRUD操作
- 支持mysql,sqlserver,oracle,postgresql,sqlite
- 支持自定義sql,sql語(yǔ)句可寫在注解中或xml中
- 支持與spring-boot集成,依賴starter即可
- 輕量級(jí),無侵入性,是官方mybatis的一種擴(kuò)展
快速開始(springboot)
- 新建一個(gè)springboot項(xiàng)目
- pom.xml添加fastmybatis-spring-boot-starter
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>fastmybatis-spring-boot-starter</artifactId>
<version>最新版本(見changelog.md)</version>
</dependency>
- 假設(shè)數(shù)據(jù)庫(kù)有張
t_user表,添加對(duì)應(yīng)的實(shí)體類TUser.java和MapperTUserMapper.java(可用fastmybatis-generator來生成) - 在
application.propertis中配置數(shù)據(jù)庫(kù)連接 - 編寫測(cè)試用例
@Autowired
TUserMapper mapper;
// 根據(jù)主鍵查詢
@Test
public void testGetById() {
TUser user = mapper.getById(3);
System.out.println(user);
}
查詢
本小節(jié)主要講解fastmybatis的查詢功能。fastmybatis提供豐富的查詢方式,滿足日常查詢所需。
分頁(yè)查詢
方式1
前端傳遞兩個(gè)分頁(yè)參數(shù)pageIndex,pageSize
// http://localhost:8080/page1?pageIndex=1&pageSize=10
@GetMapping("page1")
public List<TUser> page1(int pageIndex,int pageSize) {
Query query = new Query();
query.page(pageIndex, pageSize);
List<TUser> list = mapper.list(query);
return list;
}
方式2
PageParam里面封裝了pageIndex,pageSize參數(shù)
// http://localhost:8080/page2?pageIndex=1&pageSize=10
@GetMapping("page2")
public List<TUser> page2(PageParam param) {
Query query = param.toQuery();
List<TUser> list = mapper.list(query);
return list;
}
返回結(jié)果集和總記錄數(shù)
方式1和方式2只能查詢結(jié)果集,通常我們查詢還需返回記錄總數(shù)并返回給前端,fastmybatis的處理方式如下:
// http://localhost:8080/page3?pageIndex=1&pageSize=10
@GetMapping("page3")
public Map<String,Object> page3(PageParam param) {
Query query = param.toQuery();
List<TUser> list = mapper.list(query);
long total = mapper.getCount(query);
Map<String,Object> result = new HashMap<String, Object>();
result.put("list", list);
result.put("total", total);
return result;
}
fastmybatis提供一種更簡(jiǎn)潔的方式來處理:
// http://localhost:8080/page4?pageIndex=1&pageSize=10
@GetMapping("page4")
public PageInfo<TUser> page4(PageParam param) {
PageInfo<TUser> pageInfo = MapperUtil.query(mapper, query);
return result;
}
PageInfo里面包含了List,total信息,還包含了一些額外信息,完整數(shù)據(jù)如下:
{
"currentPageIndex": 1, // 當(dāng)前頁(yè)
"firstPageIndex": 1, // 首頁(yè)
"lastPageIndex": 2, // 尾頁(yè)
"list": [ // 結(jié)果集
{},
{}
],
"nextPageIndex": 2, // 下一頁(yè)
"pageCount": 2, // 總頁(yè)數(shù)
"pageIndex": 1, // 當(dāng)前頁(yè)
"pageSize": 10, // 每頁(yè)記錄數(shù)
"prePageIndex": 1, // 上一頁(yè)
"start": 0,
"total": 20 // 總記錄數(shù)
}
根據(jù)參數(shù)字段查詢
查詢姓名為張三的用戶
// http://localhost:8080/sch?username=張三
@GetMapping("sch")
public List<TUser> sch(String username) {
Query query = new Query();
query.eq("username", username);
List<TUser> list = mapper.list(query);
return list;
}
查詢姓名為張三并且擁有的錢大于100塊
// http://localhost:8080/sch2?username=張三
@GetMapping("sch2")
public List<TUser> sch2(String username) {
Query query = new Query();
query.eq("username", username).gt("money", 100);
List<TUser> list = mapper.list(query);
return list;
}
查詢姓名為張三并帶分頁(yè)
// http://localhost:8080/sch3?username=張三&pageIndex=1&pageSize=5
@GetMapping("sch3")
public List<TUser> sch3(String username,PageParam param) {
Query query = param.toQuery();
query.eq("username", username);
List<TUser> list = mapper.list(query);
return list;
}
查詢錢最多的前三名
// http://localhost:8080/sch4
@GetMapping("sch4")
public List<TUser> sch4() {
Query query = new Query();
query.orderby("money", Sort.DESC) // 按金額降序
.page(1, 3);
List<TUser> list = mapper.list(query);
return list;
}
將參數(shù)放在對(duì)象中查詢
// http://localhost:8080/sch5?username=張三
@GetMapping("sch5")
public List<TUser> sch5(UserParam userParam) {
Query query = userParam.toQuery();
query.eq("username", userParam.getUsername());
List<TUser> list = mapper.list(query);
return list;
}
UserParam繼承PageSortParam類,表示支持分頁(yè)和排序查詢
使用普通bean查詢
假設(shè)有個(gè)User類如下
public class User {
private Integer id;
private String userName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
我們將這個(gè)類作為查詢參數(shù),那么在springmvc中可以這樣寫:
@GetMapping(path="findUserBean.do")
public List<User> findUser(User user) {
Query query = Query.build(user);
List<User> list = dao.find(query);
return list;
}
Query query = Query.build(user);這句是將User中的屬性轉(zhuǎn)換成對(duì)應(yīng)條件,假設(shè)userName的值為"jim",那么會(huì)封裝成一個(gè)條件where user_name='jim'
瀏覽器輸入鏈接:http://localhost:8080/fastmybatis-springmvc/findUserBean.do?userName=jim
后臺(tái)將會(huì)執(zhí)行如下SQL:
SELECT id,user_name FROM user t WHERE t.user_name = ?
?的值為jim
@Condition注解
@Condition注解用來強(qiáng)化查詢,有了這個(gè)注解可以生成各種查詢條件。
@Condition注解有三個(gè)屬性:
- joint:表達(dá)式之間的連接符,AND|OR,默認(rèn)AND
- column:數(shù)據(jù)庫(kù)字段名,可選
- operator:連接符枚舉,存放了等于、大于、小于等連接符
如果要查詢id大于2的用戶只需在get方法上加上一個(gè)@Condition注解即可:
@Condition(operator=Operator.gt)
public Integer getId() {
return this.id;
}
這樣,當(dāng)id有值時(shí),會(huì)封裝成一個(gè)where id>2的條件
- 需要注意的是,如果不指定column屬性,系統(tǒng)會(huì)默認(rèn)取get方法中屬性名,然后轉(zhuǎn)換成數(shù)據(jù)庫(kù)字段名。如果需要指定數(shù)據(jù)庫(kù)字段名的話,可以使用@Condition的column屬性。
public Integer get++UserName++() {
return this.userName;
}
這種情況下會(huì)取下劃線部分字段,然后轉(zhuǎn)換成數(shù)據(jù)庫(kù)字段名。
@Condition(column="username") // 顯示指定字段名
public Integer getUserName() {
return this.userName;
}
使用@Condition可以生產(chǎn)更加靈活的條件查詢,比如需要查詢?nèi)掌跒?017-12-1~2017-12-10日的記錄,我們可以這樣寫:
@Condition(column="add_date",operator=Operator.ge)
public Date getStartDate() {
return this.startDate;
}
@Condition(column="add_date",operator=Operator.lt)
public Date getEndDate() {
return this.endDate;
}
轉(zhuǎn)換成SQL語(yǔ)句:
t.add_date>='2017-12-1' AND t.add_date<'2017-12-10'
IN查詢
假設(shè)前端頁(yè)面?zhèn)鱽矶鄠€(gè)值比如checkbox勾選多個(gè)id=[1,2],那么我們?cè)赨ser類里面可以用Integer[]或List<Integer>來接收.
private Integer[] idArr;
public void setIdArr(Integer[] idArr) {this.idArr = idArr;}
@Condition(column="id")
public Integer[] getIdArr() {return this.idArr;}
這樣會(huì)生成where id IN(1,2)條件。
排序查詢
// 根據(jù)添加時(shí)間倒序
Query query = new Query();
query.orderby("create_time",Sort.DESC);
dao.find(query);
多表關(guān)聯(lián)查詢
多表關(guān)聯(lián)查詢使用的地方很多,比如需要關(guān)聯(lián)第二張表,獲取第二張表的幾個(gè)字段,然后返回給前端。
fastmybatis的用法如下:
假如我們需要關(guān)聯(lián)第二張表user_info,篩選出user_info中的城市為杭州的數(shù)據(jù)。
Query query = new Query()
// 左連接查詢,主表的alias默認(rèn)為t
.join("LEFT JOIN user_info t2 ON t.id = t2.user_id").page(1, 5)
.eq("t2.city","杭州");
List<TUser> list = mapper.list(query);
System.out.println("==============");
for (TUser user : list) {
System.out.println(user.getId() + " " + user.getUsername());
}
System.out.println("==============");
多表關(guān)聯(lián)返回指定字段
有時(shí)候不需要全部字段,需要取表1中的幾個(gè)字段,然后取表2中的幾個(gè)字段,fastmybatis實(shí)現(xiàn)方式如下:
Query query = new Query();
// 左連接查詢,主表的alias默認(rèn)為t
query.join("LEFT JOIN user_info t2 ON t.id = t2.user_id");
// 指定返回字段
List<String> column = Arrays.asList("t2.user_id as userId", "t.username", "t2.city");
// 查詢結(jié)果返回到map中
List<Map<String, Object>> mapList = mapper.listMap(column, query);
// 再將map轉(zhuǎn)換成實(shí)體bean
List<UserInfoVo> list = MyBeanUtil.mapListToObjList(mapList, UserInfoVo.class);
執(zhí)行的SQL語(yǔ)句對(duì)應(yīng)如下:
SELECT t2.user_id as userId , t.username , t2.city
FROM `t_user` t
LEFT JOIN user_info t2 ON t.id = t2.user_id
使用@Select查詢
@Select注解是mybatis官方提供的一個(gè)功能,fastmybatis可以理解為是官方的一種擴(kuò)展,因此同樣支持此功能。
在Mapper中添加如下代碼:
@Select("select * from t_user where id=#{id}")
TUser selectById(@Param("id") int id);
編寫測(cè)試用例
@Test
public void testSelectById() {
TUser user = dao.selectById(3);
System.out.println(user.getUsername());
}
對(duì)于簡(jiǎn)單的SQL,可以用這種方式實(shí)現(xiàn)。除了@Select之外,還有@Update,@Insert,@Delete,這里就不多做演示了。
Query類詳解
Query是一個(gè)查詢參數(shù)類,配合Mapper一起使用。
參數(shù)介紹
Query里面封裝了一系列查詢參數(shù),主要分為以下幾類:
- 分頁(yè)參數(shù):設(shè)置分頁(yè)
- 排序參數(shù):設(shè)置排序字段
- 條件參數(shù):設(shè)置查詢條件
- 字段參數(shù):可返回指定字段
下面逐個(gè)講解每個(gè)參數(shù)的用法。
分頁(yè)參數(shù)
一般來說分頁(yè)的使用比較簡(jiǎn)單,通常是兩個(gè)參數(shù),
pageIndex:當(dāng)前頁(yè)索引,pageSize:每頁(yè)幾條數(shù)據(jù)。
Query類使用page(pageIdnex, pageSize)方法來設(shè)置。
假如我們要查詢第二頁(yè),每頁(yè)10條數(shù)據(jù),代碼可以這樣寫:
Query query = new Query();
query.page(2, 10);
List<User> list = dao.find(query);
如果要實(shí)現(xiàn)不規(guī)則分頁(yè),可以這樣寫:
Query query = new Query();
query.limit(3, 5) // 對(duì)應(yīng)mysql:limit 3,5
排序參數(shù)
orderby(String sortname, Sort sort)
其中sortname為數(shù)據(jù)庫(kù)字段,非javaBean屬性
- orderby(String sortname, Sort sort)則可以指定排序方式,Sort為排序方式枚舉
假如要按照添加時(shí)間倒序,可以這樣寫:
Query query = new Query();
query.orderby("create_time",Sort.DESC);
mapper.list(query);
添加多個(gè)排序字段可以在后面追加:
query.orderby("create_time",Sort.DESC).orderby("id",Sort.ASC);
條件參數(shù)
條件參數(shù)是用的最多一個(gè),因?yàn)樵诓樵冎型枰尤敫鞣N條件。
fastmybatis在條件查詢上面做了一些封裝,這里不做太多講解,只講下基本的用法,以后會(huì)單獨(dú)開一篇文章來介紹。感興趣的同學(xué)可以自行查看源碼,也不難理解。
條件參數(shù)使用非常簡(jiǎn)單,Query對(duì)象封裝一系列常用條件查詢。
- 等值查詢eq(String columnName, Object value),columnName為數(shù)據(jù)庫(kù)字段名,value為查詢的值
假設(shè)我們要查詢姓名為張三的用戶,可以這樣寫:
Query query = new Query();
query.eq("username","張三");
List<User> list = mapper.list(query);
通過方法名即可知道eq表示等于'=',同理lt表示小于<,gt表示大于>
| 查詢方式 | 說明 |
|---|---|
| eq | 等于= |
| gt | 大于> |
| lt | 小于< |
| ge | 大于等于>= |
| le | 小于等于<= |
| notEq | 不等于<> |
| like | 模糊查詢 |
| in | in()查詢 |
| notIn | not in()查詢 |
| isNull | NULL值查詢 |
| notNull | IS NOT NULL |
| notEmpty | 字段不為空,非NULL且有內(nèi)容 |
| isEmpty | 字段為NULL或者為'' |
如果上述方法還不能滿足查詢需求的話,我們可以使用自定sql的方式來編寫查詢條件,方法為:
Query query = new Query();
query.sql(" username='Jim' OR username='Tom'");
注意:sql()方法不會(huì)處理sql注入問題,因此盡量少用。
自定義SQL
方式1
直接寫在Mapper.java中
public interface TUserMapper extends CrudMapper<TUser, Integer> {
// 自定義sql,官方自帶,不需要寫xml
/**
* 修改用戶名
* @param id
* @param username
* @return 返回影響行數(shù)
*/
@Update("update t_user set username = #{username} where id = #{id}")
int updateById(@Param("id") int id, @Param("username") String username);
}
簡(jiǎn)單SQL可采用這種形式。
方式2
fastmybatis提供的Mapper已經(jīng)滿足大部分的操作需求,但是有些復(fù)雜的sql語(yǔ)句還是需要寫在xml文件中。fastmybatis同樣支持將sql語(yǔ)句寫在xml中,具體配置如下:
- 在application.properties添加一句,指定xml文件存放路徑
mybatis.mapper-locations=classpath:/mybatis/mapper/*.xml
- 在resources/mybatis/mapper目錄下新建一個(gè)xml文件TUserMapper.xml,內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mayapp.mapper.TUserMapper">
<select id="selectByName" parameterType="String" resultMap="baseResultMap">
select * from t_user t where t.username = #{username} limit 1
</select>
</mapper>
這個(gè)xml文件跟其它的mybatis配置文件一樣,baseResultMap沒有看到定義,但是確實(shí)存在,因?yàn)檫@個(gè)是fastmybatis提供的一個(gè)內(nèi)置resultMap。
- 在TUseroMapper.java中添加:
TUser selectByName(@Param("username")String username);
- 編寫單元測(cè)試用例
@Test
public void testSelectByName() {
TUser user = dao.selectByName("張三");
System.out.println(user.getUsername());
}
多文件同一個(gè)namespace
在以往的開發(fā)過程中,一個(gè)Mapper對(duì)應(yīng)一個(gè)xml文件(namespace)。如果多人同時(shí)在一個(gè)xml中寫SQL的話會(huì)造成各種沖突(雖然能夠最終被解決)。
fastmybatis打破這種常規(guī),允許不同的xml文件定義相同的namespace,程序啟動(dòng)時(shí)會(huì)自動(dòng)把他們的內(nèi)容合并到同一個(gè)文件當(dāng)中去。
- 張三的UserMapper_zs.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mayapp.mapper.TUserMapper">
<select id="selectByName" parameterType="String" resultMap="baseResultMap">
select * from t_user t where t.username = #{username} limit 1
</select>
</mapper>
- 李四的UserMapper_ls.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mayapp.mapper.TUserMapper">
<select id="updateUser" parameterType="String" resultMap="baseResultMap">
update t_user set username = #{username} where id=#{id}
</select>
</mapper>
最終會(huì)合并成
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mayapp.mapper.TUserMapper">
<!-- 張三部分 -->
<select id="selectByName" parameterType="String" resultMap="baseResultMap">
select * from t_user t where t.username = #{username} limit 1
</select>
<!-- 李四部分 -->
<select id="updateUser" parameterType="String" resultMap="baseResultMap">
update t_user set username = #{username} where id=#{id}
</select>
</mapper>
這樣也體現(xiàn)了開閉原則,即新增一個(gè)功能只需要新增一個(gè)文件就行,不需要修改原來的文件。
如果SQL寫多了還可以把它們進(jìn)行分類,放到不同的xml中,便于管理。
注:合并動(dòng)作是在啟動(dòng)時(shí)進(jìn)行的,并不會(huì)生成一個(gè)真實(shí)的文件。
字段自動(dòng)填充
填充器設(shè)置
假設(shè)數(shù)據(jù)庫(kù)表里面有兩個(gè)時(shí)間字段gmt_create,gmt_update。
當(dāng)進(jìn)行insert操作時(shí)gmt_create,gmt_update字段需要更新。當(dāng)update時(shí),gmt_update字段需要更新。
通常的做法是通過Entity手動(dòng)設(shè)置:
User user = new User();
user.setGmtCreate(new Date());
user.setGmtUpdate(new Date());
因?yàn)楸碓O(shè)計(jì)的時(shí)候大部分都有這兩個(gè)字段,所以對(duì)每張表都進(jìn)行手動(dòng)設(shè)置的話很容易錯(cuò)加、漏加。
fastmybatis提供了兩個(gè)輔助類DateFillInsert和DateFillUpdate,用來處理添加修改時(shí)的時(shí)間字段自動(dòng)填充。配置了這兩個(gè)類之后,時(shí)間字段將會(huì)自動(dòng)設(shè)置。
配置方式如下:
EasymybatisConfig config = new EasymybatisConfig();
config.setFills(Arrays.asList(
new DateFillInsert()
,new DateFillUpdate()
));
在spring的xml中配置如下:
<bean id="sqlSessionFactory"
class="com.gitee.fastmybatis.core.ext.SqlSessionFactoryBeanExt">
<property name="dataSource" ref="dataSource" />
<property name="configLocation">
<value>classpath:mybatis/mybatisConfig.xml</value>
</property>
<property name="mapperLocations">
<list>
<value>classpath:mybatis/mapper/*.xml</value>
</list>
</property>
<!-- 以下是附加屬性 -->
<!-- dao所在的包名,跟MapperScannerConfigurer的basePackage一致
多個(gè)用;隔開
-->
<property name="basePackage" value="com.myapp.dao" />
<property name="config">
<bean class="com.gitee.fastmybatis.core.EasymybatisConfig">
<!-- 定義填充器 -->
<property name="fills">
<list>
<bean class="com.gitee.fastmybatis.core.support.DateFillInsert"/>
<bean class="com.gitee.fastmybatis.core.support.DateFillUpdate"/>
</list>
</property>
</bean>
</property>
</bean>
springboot中可以這樣定義:
在application.properties中添加:
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=
如果要指定字段名,可以寫成:
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=add_time
自定義填充器
除了使用fastmybatis默認(rèn)提供的填充之外,我們還可以自定義填充。
自定義填充類要繼承FillHandler<T>類。
<T> 表示填充字段類型,如Date,String,BigDecimal,Boolean。
實(shí)戰(zhàn)(springboot)
現(xiàn)在有個(gè)remark字段,需要在insert時(shí)初始化為“備注默認(rèn)內(nèi)容”,新建一個(gè)StringRemarkFill類如下:
public class StringRemarkFill extends FillHandler<String> {
@Override
public String getColumnName() {
return "remark";
}
@Override
public FillType getFillType() {
return FillType.INSERT;
}
@Override
protected Object getFillValue(String defaultValue) {
return "備注默認(rèn)內(nèi)容";
}
}
StringRemarkFill類中有三個(gè)重寫方法:
- getColumnName() : 指定表字段名
- getFillType() : 填充方式,F(xiàn)illType.INSERT:僅insert時(shí)填充; FillType.UPDATE:insert,update時(shí)填充
- getFillValue(String defaultValue) :返回填充內(nèi)容
然后在application.properties中添加:
mybatis.fill.com.xx.StringRemarkFill=
這樣就配置完畢了,調(diào)用dao.save(user);時(shí)會(huì)自動(dòng)填充remark字段。
指定目標(biāo)類
上面說到StringRemarkFill填充器,它作用在所有實(shí)體類上,也就是說實(shí)體類如果有remark字段都會(huì)自動(dòng)填充。這樣顯然是不合理的,解決辦法是指定特定的實(shí)體類。只要重寫FillHandler類的getTargetEntityClasses()方法即可。
@Override
public Class<?>[] getTargetEntityClasses() {
return new Class<?>[] { TUser.class };
}
這樣就表示作用在TUser類上,多個(gè)類可以追加。最終代碼如下:
public class StringRemarkFill extends FillHandler<String> {
@Override
public String getColumnName() {
return "remark";
}
@Override
public Class<?>[] getTargetEntityClasses() {
return new Class<?>[] { TUser.class }; // 只作用在TUser類上
}
@Override
public FillType getFillType() {
return FillType.INSERT;
}
@Override
protected Object getFillValue(String defaultValue) {
return "備注默認(rèn)內(nèi)容"; // insert時(shí)填充的內(nèi)容
}
}
關(guān)于自動(dòng)填充的原理是基于mybatis的TypeHandler實(shí)現(xiàn)的,這里就不多做介紹了。感興趣的同學(xué)可以查看FillHandler<T>源碼。