簡介
fastmybatis是一個mybatis開發框架,其宗旨為:簡單、快速、有效。
- 零配置快速上手
- 無需編寫xml文件即可完成CRUD操作
- 支持mysql,sqlserver,oracle,postgresql,sqlite
- 支持自定義sql,sql語句可寫在注解中或xml中
- 支持與spring-boot集成,依賴starter即可
- 輕量級,無侵入性,是官方mybatis的一種擴展
快速開始(springboot)
- 新建一個springboot項目
- pom.xml添加fastmybatis-spring-boot-starter
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>fastmybatis-spring-boot-starter</artifactId>
<version>最新版本(見changelog.md)</version>
</dependency>
- 假設數據庫有張
t_user
表,添加對應的實體類TUser.java
和MapperTUserMapper.java
(可用fastmybatis-generator來生成) - 在
application.propertis
中配置數據庫連接 - 編寫測試用例
@Autowired
TUserMapper mapper;
// 根據主鍵查詢
@Test
public void testGetById() {
TUser user = mapper.getById(3);
System.out.println(user);
}
查詢
本小節主要講解fastmybatis的查詢功能。fastmybatis提供豐富的查詢方式,滿足日常查詢所需。
分頁查詢
方式1
前端傳遞兩個分頁參數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參數
// 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;
}
返回結果集和總記錄數
方式1和方式2只能查詢結果集,通常我們查詢還需返回記錄總數并返回給前端,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提供一種更簡潔的方式來處理:
// 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信息,還包含了一些額外信息,完整數據如下:
{
"currentPageIndex": 1, // 當前頁
"firstPageIndex": 1, // 首頁
"lastPageIndex": 2, // 尾頁
"list": [ // 結果集
{},
{}
],
"nextPageIndex": 2, // 下一頁
"pageCount": 2, // 總頁數
"pageIndex": 1, // 當前頁
"pageSize": 10, // 每頁記錄數
"prePageIndex": 1, // 上一頁
"start": 0,
"total": 20 // 總記錄數
}
根據參數字段查詢
查詢姓名為張三的用戶
// 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;
}
查詢姓名為張三并帶分頁
// 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;
}
將參數放在對象中查詢
// 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類,表示支持分頁和排序查詢
使用普通bean查詢
假設有個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;
}
}
我們將這個類作為查詢參數,那么在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中的屬性轉換成對應條件,假設userName的值為"jim",那么會封裝成一個條件where user_name='jim'
瀏覽器輸入鏈接:http://localhost:8080/fastmybatis-springmvc/findUserBean.do?userName=jim
后臺將會執行如下SQL:
SELECT id,user_name FROM user t WHERE t.user_name = ?
?的值為jim
@Condition注解
@Condition注解用來強化查詢,有了這個注解可以生成各種查詢條件。
@Condition注解有三個屬性:
- joint:表達式之間的連接符,AND|OR,默認AND
- column:數據庫字段名,可選
- operator:連接符枚舉,存放了等于、大于、小于等連接符
如果要查詢id大于2的用戶只需在get方法上加上一個@Condition注解即可:
@Condition(operator=Operator.gt)
public Integer getId() {
return this.id;
}
這樣,當id有值時,會封裝成一個where id>2的條件
- 需要注意的是,如果不指定column屬性,系統會默認取get方法中屬性名,然后轉換成數據庫字段名。如果需要指定數據庫字段名的話,可以使用@Condition的column屬性。
public Integer get++UserName++() {
return this.userName;
}
這種情況下會取下劃線部分字段,然后轉換成數據庫字段名。
@Condition(column="username") // 顯示指定字段名
public Integer getUserName() {
return this.userName;
}
使用@Condition可以生產更加靈活的條件查詢,比如需要查詢日期為2017-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;
}
轉換成SQL語句:
t.add_date>='2017-12-1' AND t.add_date<'2017-12-10'
IN查詢
假設前端頁面傳來多個值比如checkbox勾選多個id=[1,2],那么我們在User類里面可以用Integer[]或List<Integer>來接收.
private Integer[] idArr;
public void setIdArr(Integer[] idArr) {this.idArr = idArr;}
@Condition(column="id")
public Integer[] getIdArr() {return this.idArr;}
這樣會生成where id IN(1,2)條件。
排序查詢
// 根據添加時間倒序
Query query = new Query();
query.orderby("create_time",Sort.DESC);
dao.find(query);
多表關聯查詢
多表關聯查詢使用的地方很多,比如需要關聯第二張表,獲取第二張表的幾個字段,然后返回給前端。
fastmybatis的用法如下:
假如我們需要關聯第二張表user_info
,篩選出user_info中的城市為杭州的數據。
Query query = new Query()
// 左連接查詢,主表的alias默認為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("==============");
多表關聯返回指定字段
有時候不需要全部字段,需要取表1中的幾個字段,然后取表2中的幾個字段,fastmybatis實現方式如下:
Query query = new Query();
// 左連接查詢,主表的alias默認為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");
// 查詢結果返回到map中
List<Map<String, Object>> mapList = mapper.listMap(column, query);
// 再將map轉換成實體bean
List<UserInfoVo> list = MyBeanUtil.mapListToObjList(mapList, UserInfoVo.class);
執行的SQL語句對應如下:
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官方提供的一個功能,fastmybatis可以理解為是官方的一種擴展,因此同樣支持此功能。
在Mapper中添加如下代碼:
@Select("select * from t_user where id=#{id}")
TUser selectById(@Param("id") int id);
編寫測試用例
@Test
public void testSelectById() {
TUser user = dao.selectById(3);
System.out.println(user.getUsername());
}
對于簡單的SQL,可以用這種方式實現。除了@Select之外,還有@Update,@Insert,@Delete,這里就不多做演示了。
Query類詳解
Query是一個查詢參數類,配合Mapper一起使用。
參數介紹
Query里面封裝了一系列查詢參數,主要分為以下幾類:
- 分頁參數:設置分頁
- 排序參數:設置排序字段
- 條件參數:設置查詢條件
- 字段參數:可返回指定字段
下面逐個講解每個參數的用法。
分頁參數
一般來說分頁的使用比較簡單,通常是兩個參數,
pageIndex:當前頁索引,pageSize:每頁幾條數據。
Query類使用page(pageIdnex, pageSize)方法來設置。
假如我們要查詢第二頁,每頁10條數據,代碼可以這樣寫:
Query query = new Query();
query.page(2, 10);
List<User> list = dao.find(query);
如果要實現不規則分頁,可以這樣寫:
Query query = new Query();
query.limit(3, 5) // 對應mysql:limit 3,5
排序參數
orderby(String sortname, Sort sort)
其中sortname為數據庫字段,非javaBean屬性
- orderby(String sortname, Sort sort)則可以指定排序方式,Sort為排序方式枚舉
假如要按照添加時間倒序,可以這樣寫:
Query query = new Query();
query.orderby("create_time",Sort.DESC);
mapper.list(query);
添加多個排序字段可以在后面追加:
query.orderby("create_time",Sort.DESC).orderby("id",Sort.ASC);
條件參數
條件參數是用的最多一個,因為在查詢中往往需要加入各種條件。
fastmybatis在條件查詢上面做了一些封裝,這里不做太多講解,只講下基本的用法,以后會單獨開一篇文章來介紹。感興趣的同學可以自行查看源碼,也不難理解。
條件參數使用非常簡單,Query對象封裝一系列常用條件查詢。
- 等值查詢eq(String columnName, Object value),columnName為數據庫字段名,value為查詢的值
假設我們要查詢姓名為張三的用戶,可以這樣寫:
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且有內容 |
isEmpty | 字段為NULL或者為'' |
如果上述方法還不能滿足查詢需求的話,我們可以使用自定sql的方式來編寫查詢條件,方法為:
Query query = new Query();
query.sql(" username='Jim' OR username='Tom'");
注意:sql()方法不會處理sql注入問題,因此盡量少用。
自定義SQL
方式1
直接寫在Mapper.java中
public interface TUserMapper extends CrudMapper<TUser, Integer> {
// 自定義sql,官方自帶,不需要寫xml
/**
* 修改用戶名
* @param id
* @param username
* @return 返回影響行數
*/
@Update("update t_user set username = #{username} where id = #{id}")
int updateById(@Param("id") int id, @Param("username") String username);
}
簡單SQL可采用這種形式。
方式2
fastmybatis提供的Mapper已經滿足大部分的操作需求,但是有些復雜的sql語句還是需要寫在xml文件中。fastmybatis同樣支持將sql語句寫在xml中,具體配置如下:
- 在application.properties添加一句,指定xml文件存放路徑
mybatis.mapper-locations=classpath:/mybatis/mapper/*.xml
- 在resources/mybatis/mapper目錄下新建一個xml文件TUserMapper.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>
這個xml文件跟其它的mybatis配置文件一樣,baseResultMap沒有看到定義,但是確實存在,因為這個是fastmybatis提供的一個內置resultMap。
- 在TUseroMapper.java中添加:
TUser selectByName(@Param("username")String username);
- 編寫單元測試用例
@Test
public void testSelectByName() {
TUser user = dao.selectByName("張三");
System.out.println(user.getUsername());
}
多文件同一個namespace
在以往的開發過程中,一個Mapper對應一個xml文件(namespace)。如果多人同時在一個xml中寫SQL的話會造成各種沖突(雖然能夠最終被解決)。
fastmybatis打破這種常規,允許不同的xml文件定義相同的namespace,程序啟動時會自動把他們的內容合并到同一個文件當中去。
- 張三的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>
最終會合并成
<?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>
這樣也體現了開閉原則,即新增一個功能只需要新增一個文件就行,不需要修改原來的文件。
如果SQL寫多了還可以把它們進行分類,放到不同的xml中,便于管理。
注:合并動作是在啟動時進行的,并不會生成一個真實的文件。
字段自動填充
填充器設置
假設數據庫表里面有兩個時間字段gmt_create,gmt_update。
當進行insert操作時gmt_create,gmt_update字段需要更新。當update時,gmt_update字段需要更新。
通常的做法是通過Entity手動設置:
User user = new User();
user.setGmtCreate(new Date());
user.setGmtUpdate(new Date());
因為表設計的時候大部分都有這兩個字段,所以對每張表都進行手動設置的話很容易錯加、漏加。
fastmybatis提供了兩個輔助類DateFillInsert和DateFillUpdate,用來處理添加修改時的時間字段自動填充。配置了這兩個類之后,時間字段將會自動設置。
配置方式如下:
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一致
多個用;隔開
-->
<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默認提供的填充之外,我們還可以自定義填充。
自定義填充類要繼承FillHandler<T>類。
<T> 表示填充字段類型,如Date,String,BigDecimal,Boolean。
實戰(springboot)
現在有個remark字段,需要在insert時初始化為“備注默認內容”,新建一個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 "備注默認內容";
}
}
StringRemarkFill類中有三個重寫方法:
- getColumnName() : 指定表字段名
- getFillType() : 填充方式,FillType.INSERT:僅insert時填充; FillType.UPDATE:insert,update時填充
- getFillValue(String defaultValue) :返回填充內容
然后在application.properties中添加:
mybatis.fill.com.xx.StringRemarkFill=
這樣就配置完畢了,調用dao.save(user);時會自動填充remark字段。
指定目標類
上面說到StringRemarkFill填充器,它作用在所有實體類上,也就是說實體類如果有remark字段都會自動填充。這樣顯然是不合理的,解決辦法是指定特定的實體類。只要重寫FillHandler類的getTargetEntityClasses()方法即可。
@Override
public Class<?>[] getTargetEntityClasses() {
return new Class<?>[] { TUser.class };
}
這樣就表示作用在TUser類上,多個類可以追加。最終代碼如下:
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 "備注默認內容"; // insert時填充的內容
}
}
關于自動填充的原理是基于mybatis的TypeHandler實現的,這里就不多做介紹了。感興趣的同學可以查看FillHandler<T>源碼。