雖然 spring5 也推出了 WebFlux 這一套異步技術棧,這種極大提升吞吐的玩法在 node 里玩的風生水起,但 java 世界里異步依舊不是主流,Vertx 倒是做了不少對異步的支持,但是其對于數據訪問層的封裝依舊還是挺精簡的,傳統的 javaer 還是受不了這種沒有對象映射的工具庫,于是我嘗試將 Mybatis 移植到了異步驅動上,讓數據訪問層的工作變得更簡單一些。給個例子:
@Sql(User.class)
public interface CommonMapper {
@Select(columns = "id,age,username")
@OrderBy("id desc")
@Page
@ModelConditions({
@ModelCondition(field = "username", criterion = Criterions.EQUAL),
@ModelCondition(field = "maxAge", column = "age", criterion = Criterions.LESS),
@ModelCondition(field = "minAge", column = "age", criterion = Criterions.GREATER)
})
void query(UserSearch userSearch, DataHandler> handler);
}
上面是 mapper 接口定義,方法的最后一個參數因為異步的原因所以變成了一個回調,不同的是有很多注解來表達 sql,看到這些注解應該不難猜出 sql 語句吧。如果不喜歡你當然可以繼續使用 mapper.xml 的方式來寫 sql。
更多內容移步代碼庫吧~
AsyncDao
asyncDao是一款異步非阻塞模型下的數據訪問層工具。
MySQL only. 基于MySQL的異步驅動
借鑒了Mybatis的mapping 和 dynamicSQL的內容,Mybatiser可以無縫切換
注解表達SQL的能力
事務支持
SpringBoot支持
Mybatis like
使用上與Mybatis幾乎一致,由于異步非阻塞的關系,數據的返回都會通過回調DataHandler來完成,所以方法定義參數的最后一個一定是DataHandler類型。由于需要提取方法的參數名,于是需要加上編譯參數-parameters,請將它在IDE和maven里配置上。
public interface CommonDao {
void query(User user, DataHandler> handler);
void querySingle(User user, DataHandler handler);
void querySingleMap(User user, DataHandler handler);
void insert(User user,DataHandler handler);
void update(User user,DataHandler handler);
void delete(User user,DataHandler handler);
}
mapper.xml與Mybatis幾乎一致的寫法(覆蓋常見標簽,一些不常用標簽可能不支持,動態SQL建議使用注解SQL功能)
select * from T_User
AND username = #{user.username}
OR age > #{user.age}
order by id desc
insert into T_User
old_address,
created_at,
password,
now_address,
state,
age,
username,
updated_at,
#{user.oldAddress},
#{user.createdAt},
#{user.password},
#{user.nowAddress},
#{user.state},
#{user.age},
#{user.username},
#{user.updatedAt},
update T_User
password=#{user.password},
age=#{user.age},
where id = #{user.id}
注解SQL
在XML里寫SQL對于一些常見SQL實在是重復勞動,so這里允許你利用注解來表達SQL,該怎么做呢?
Table與Model關聯
@Table(name = "T_User")
public class User {
@Id("id")
private Long id;
//建議全部用包裝類型,并注意mysql中字段類型與java類型的對應關系,mysql的int不會自動裝換到這里的long
private String username;
private Integer age;
@Column("now_address")
private String nowAddress;
@Column("created_at")
private LocalDateTime createdAt;
//asyncDao 里sql的時間類型都用joda,注意不是JDK8提供的那個,而是第三方包org.joda.time
@Ignore
private String remrk;
@Table記錄數據表的名字 @Id記錄主鍵信息 @Column映射了表字段和屬性的關系,如果表字段和類屬性同名,那么可以省略這個注解 @Ingore忽略這個類屬性,沒有哪個表字段與它關聯。
定義接口
@Sql(User.class)
public interface CommonDao {
@Select(columns = "id,age,username")
@OrderBy("id desc")
@Page
@ModelConditions({
@ModelCondition(field = "username", criterion = Criterions.EQUAL),
@ModelCondition(field = "maxAge", column = "age", criterion = Criterions.LESS),
@ModelCondition(field = "minAge", column = "age", criterion = Criterions.GREATER)
})
void query(UserSearch userSearch, DataHandler> handler);
@Select(columns = "age,username")
@OrderBy("id desc")
void queryParam(@Condition String username,
@Condition(criterion = Criterions.GREATER) Integer age,
@OffSet int offset,
@Limit int limit,
DataHandler> handler);
@Select(columns = "username,age", sqlMode = SqlMode.COMMON)
void queryList(@Condition(criterion = Criterions.IN, column = "id") int[] ids, DataHandler> handler);
@Insert(useGeneratedKeys = true, keyProperty = "id")
void insert(User user, DataHandler handler);
@Update
@ModelConditions(@ModelCondition(field = "id"))
void update(User user, DataHandler handler);
@Delete
@ModelConditions(@ModelCondition(field = "id"))
void delete(User user, DataHandler handler);
}
看到這些注解你應該能猜出來SQL長什么樣,接下來解釋一下這些注解
查詢
@Select(columns = "id,age,username")
@OrderBy("id desc")
@Page
@ModelConditions({
@ModelCondition(field = "username", criterion = Criterions.EQUAL),
@ModelCondition(field = "maxAge", column = "age", criterion = Criterions.LESS),
@ModelCondition(field = "minAge", column = "age", criterion = Criterions.GREATER)
})
void query(UserSearch userSearch, DataHandler> handler);
@Select
columns:默認 select *可以配置columns("username,age")選擇部分字段;
SqlMode:有兩個選擇,SqlMode.SELECTIVE 和 SqlMode.COMMON,區別是selective會檢查查詢條件的字段是否為null來實現動態的查詢,即值為null時不會成為查詢條件。并且@Select,@Count,@Update,@Delete都有selective這個屬性。
@Condition
criterion:查詢條件,=,,in等,具體見Criterions
column:與表字段的對應,若與字段名相同可不配置
attach:連接 and,or, 默認是and
test:SqlMode為selective下的判斷表達式,類似Mybatis里的test屬性,動態化查詢條件
@Limit,@OffSet為分頁字段。
方法的參數不加任何注解一樣會被當做查詢條件,如下面兩個函數效果是一樣的:
@Select()
void queryUser(Integer age,DataHandler> handler);
@Select()
void queryUser(@Condition(criterion = Criterions.EQUAL, column = "age") Integer age,DataHandler> handler);
查詢Model
上面的例子在查詢條件比較多時方法參數會比較多,我們可以把查詢條件封裝到一個類里,使用@ModelConditions來注解查詢條件,注意被@ModelConditions注解的方法只能有兩個參數,一個是查詢model,一個是DataHandler。
@Select
@Page
@ModelConditions({
@ModelCondition(field = "username", criterion = Criterions.EQUAL),
@ModelCondition(field = "minAge", column = "age", criterion = Criterions.GREATER),
@ModelCondition(field = "maxAge", column = "age", criterion = Criterions.LESS),
@ModelCondition(field = "ids", column = "id", criterion = Criterions.IN)
})
void queryUser5(UserSearch userSearch,DataHandler> handler);
@ModelCondition
field:必填,查詢條件中類對應的屬性
column:對應的表字段
test:動態SQL的判斷表達式
@Page只能用在ModelConditions下的查詢,并且方法參數的那個類應該有offset,limit這兩個屬性,或者 使用@Page(offsetField = "offset",limitField = "limit")指定具體字段
統計
@Count
void count(DataHandler handler);//返回Long類型
插入
@Insert(useGeneratedKeys = true, keyProperty = "id")//返回自增id
void insert(User user, DataHandler handler);
更新
@Update(columns = "username,age")//選擇更新某幾個列
void update(User user, DataHandler handler);//返回affectedRows
刪除
@Delete
int delete(@Condition(criterion = Criterions.GREATER, column = "age") int min,
@Condition(criterion = Criterions.LESS, column = "age") int max,
DataHandler handler);
@Delete
@ModelConditions(@ModelCondition(field = "id"))
void delete(User user, DataHandler handler);
使用
簡單的編程使用
AsyncConfig asyncConfig = new AsyncConfig();
PoolConfiguration configuration = new PoolConfiguration("username", "localhost", 3306, "password", "database-name");
asyncConfig.setPoolConfiguration(configuration);
asyncConfig.setMapperPackages("com.tg.async.mapper");//mapper接口
asyncConfig.setXmlLocations("mapper/");//xml目錄,classpath的相對路徑,不支持絕對路徑
AsyncDaoFactory asyncDaoFactory = AsyncDaoFactory.build(asyncConfig);
CommonDao commonDao = asyncDaoFactory.getMapper(CommonDao.class);
UserSearch userSearch = new UserSearch();
userSearch.setUsername("ha");
userSearch.setMaxAge(28);
userSearch.setMinAge(8);
userSearch.setLimit(5);
CountDownLatch latch = new CountDownLatch(1);
commonDao.query(user, users -> {
System.out.println(users);
latch.countDown();
});
latch.await();
事務
Mybatis和Spring體系里有一個非常好用的@Translactional注解,我們知道事務本質就是依賴connection的rollback等操作,那么一個事務下多個SQL就要共用這一個connection,如何共享呢?傳統的阻塞體系下ThreadLocal就成了實現這一點的完美解決方案。那么在異步世界里,要實現mybatis-spring一樣的上層Api來完成事務操作是一件非常困難的事,難點就在于Api太上層,以至于無法實現connection共享。于是這里自能退而求其次,使用編程式的方式來使用事務,抽象出一個Translaction,具體的mapper通過translaction.getMapper()來獲取,這樣通過同一個Translaction得到的Mapper都將共用一個connection。
CountDownLatch latch = new CountDownLatch(1);
AsyncConfig asyncConfig = new AsyncConfig();
PoolConfiguration configuration = new PoolConfiguration("username", "localhost", 3306, "password", "database-name");
asyncConfig.setPoolConfiguration(configuration);
asyncConfig.setMapperPackages("com.tg.async.mapper");
asyncConfig.setXmlLocations("mapper/");
asyncDaoFactory = AsyncDaoFactory.build(asyncConfig);
asyncDaoFactory.startTranslation(res -> {
Translaction translaction = res.result();
System.out.println(translaction);
CommonDao commonDao = translaction.getMapper(CommonDao.class);
User user = new User();
user.setUsername("insert");
user.setPassword("1234");
user.setAge(28);
commonDao.insert(user, id -> {
System.out.println(id);
translaction.rollback(Void -> {
latch.countDown();
});
});
});
latch.await();
SpringBoot
雖然Spring5推出了WebFlux,但異步體系在Spring里依舊不是主流。在異步化改造的過程中,大部分人也往往會保留Spring的IOC,而將其他交給Vertx,所以asyncDao對于Spring的支持就是將Mapper注入IOC容器。
quick start
YAML配置文件:
async:
dao:
mapperLocations: /mapper #xml目錄,classpath的相對路徑,不支持絕對路徑
basePackages: com.tg.mapper #mapper所在包
username: username
host: localhost
port: 3306
password: pass
database: database-name
maxTotal: 12
maxIdle: 12
minIdle: 1
maxWaitMillis: 10000
添加@Mapper來實現注入
@Mapper
@Sql(User.class)
public interface CommonDao {
@Select(columns = "id,age,username")
@OrderBy("id desc")
@Page(offsetField = "offset", limitField = "limit")
@ModelConditions({
@ModelCondition(field = "username", criterion = Criterions.EQUAL),
@ModelCondition(field = "maxAge", column = "age", criterion = Criterions.LESS),
@ModelCondition(field = "minAge", column = "age", criterion = Criterions.GREATER)
})
void query(UserSearch userSearch, DataHandler> handler);
}
通過@EnableAsyncDao來開啟支持,簡單示例:
@SpringBootApplication
@EnableAsyncDao
public class DemoApplication {
public static void main(String[] args){
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class);
CommonDao commonDao = applicationContext.getBean(CommonDao.class);
UserSearch userSearch = new UserSearch();
userSearch.setUsername("ha");
userSearch.setMaxAge(28);
userSearch.setMinAge(8);
userSearch.setLimit(5);
commonDao.query(userSearch, users -> {
System.out.println("result: " + users);
});
}
}