twogoods

twogoods 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

twogoods 发布了文章 · 2018-08-29

基于 mysql 异步驱动的非阻塞 Mybatis

虽然 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<List<User>> 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<List<User>> handler);

    void querySingle(User user, DataHandler<User> handler);

    void querySingleMap(User user, DataHandler<Map> handler);

    void insert(User user,DataHandler<Long> handler);

    void update(User user,DataHandler<Long> handler);

    void delete(User user,DataHandler<Long> handler);
}

mapper.xml与Mybatis几乎一致的写法(覆盖常见标签,一些不常用标签可能不支持,动态SQL建议使用注解SQL功能)

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.tg.async.mapper.CommonDao">
    <resultMap id="BaseResultMap" type="com.tg.async.mapper.User">
        <id column="id" property="id"/>
        <result column="old_address" property="oldAddress"/>
        <result column="created_at" property="createdAt"/>
        <result column="password" property="password"/>
        <result column="now_address" property="nowAddress"/>
        <result column="state" property="state"/>
        <result column="age" property="age"/>
        <result column="username" property="username"/>
        <result column="updated_at" property="updatedAt"/>
    </resultMap>

    <select id="query" resultMap="BaseResultMap">select * from T_User
        <where>
            <if test="user.username!=null and user.username!=''">AND username = #{user.username}</if>
            <if test="user.age != null">OR age > #{user.age}</if>
        </where>
        order by id desc
    </select>


    <insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into T_User
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="user.oldAddress != null">old_address,</if>
            <if test="user.createdAt != null">created_at,</if>
            <if test="user.password != null">password,</if>
            <if test="user.nowAddress != null">now_address,</if>
            <if test="user.state != null">state,</if>
            <if test="user.age != null">age,</if>
            <if test="user.username != null">username,</if>
            <if test="user.updatedAt != null">updated_at,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="user.oldAddress != null">#{user.oldAddress},</if>
            <if test="user.createdAt != null">#{user.createdAt},</if>
            <if test="user.password != null">#{user.password},</if>
            <if test="user.nowAddress != null">#{user.nowAddress},</if>
            <if test="user.state != null">#{user.state},</if>
            <if test="user.age != null">#{user.age},</if>
            <if test="user.username != null">#{user.username},</if>
            <if test="user.updatedAt != null">#{user.updatedAt},</if>
        </trim>
    </insert>

    <update id="update">
        update T_User
        <set>
            <if test="user.password != null">password=#{user.password},</if>
            <if test="user.age != null">age=#{user.age},</if>
        </set>
        where id = #{user.id}
    </update>
</mapper>

注解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<List<User>> 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<List<User>> handler);


    @Select(columns = "username,age", sqlMode = SqlMode.COMMON)
    void queryList(@Condition(criterion = Criterions.IN, column = "id") int[] ids, DataHandler<List<User>> handler);

    @Insert(useGeneratedKeys = true, keyProperty = "id")
    void insert(User user, DataHandler<Long> handler);

    @Update
    @ModelConditions(@ModelCondition(field = "id"))
    void update(User user, DataHandler<Long> handler);

    @Delete
    @ModelConditions(@ModelCondition(field = "id"))
    void delete(User user, DataHandler<Long> 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<List<User>> 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<if test="username != null">里的test属性,动态化查询条件

@Limit@OffSet为分页字段。
方法的参数不加任何注解一样会被当做查询条件,如下面两个函数效果是一样的:

@Select()
void queryUser(Integer age,DataHandler<List<User>> handler);

@Select()
void queryUser(@Condition(criterion = Criterions.EQUAL, column = "age") Integer age,DataHandler<List<User>> 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<List<User>> handler);
@ModelCondition
  • field:必填,查询条件中类对应的属性
  • column:对应的表字段
  • test:动态SQL的判断表达式

@Page只能用在ModelConditions下的查询,并且方法参数的那个类应该有offsetlimit这两个属性,或者 使用@Page(offsetField = "offset",limitField = "limit")指定具体字段

统计

@Count
void count(DataHandler<Integer> handler);//返回Long类型

插入

@Insert(useGeneratedKeys = true, keyProperty = "id")//返回自增id
void insert(User user, DataHandler<Long> handler);

更新

@Update(columns = "username,age")//选择更新某几个列
void update(User user, DataHandler<Long> handler);//返回affectedRows

删除

@Delete
int delete(@Condition(criterion = Criterions.GREATER, column = "age") int min,
          @Condition(criterion = Criterions.LESS, column = "age") int max,
          DataHandler<Long> handler);

@Delete
@ModelConditions(@ModelCondition(field = "id"))
void delete(User user, DataHandler<Long> 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<List<User>> 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);
        });
    }
}
查看原文

赞 0 收藏 0 评论 0

twogoods 回答了问题 · 2018-01-29

请问go有没有热更新的机制?如果没有,如何设计一个热更新机制?

优雅重启是一个方向,但你们的服务还是单机部署?内存缓存没有持久化?不能reload?

关注 8 回答 6

twogoods 赞了文章 · 2018-01-17

puppeteer新手入门(chromium下载跳坑)

puppeteer简介

puppeteer 翻译是操纵木偶的人,利用这个工具,我们能做一个操纵页面的人。puppeteer是一个nodejs的库,支持调用Chrome的API来操纵Web,相比较Selenium或是PhantomJs,它最大的特点就是它的操作Dom可以完全在内存中进行模拟既在V8引擎中处理而不打开浏览器,而且关键是这个是Chrome团队在维护,会拥有更好的兼容性和前景。

puppeteer功能

  • 生成页面的截图和PDF。
  • 抓取SPA并生成预先呈现的内容(即“SSR”)。
  • 从网站抓取你需要的内容。
  • 自动表单提交,UI测试,键盘输入等
  • 创建一个最新的自动化测试环境。使用最新的JavaScript和浏览器功能,直接在最新版本的Chrome中运行测试。
  • 捕获您的网站的时间线跟踪,以帮助诊断性能问题。

puppeteer轻松入门

1. 环境和安装

Puppeteer 至少需要 Node v6.4.0,如要使用 async / await,只有 Node v7.6.0 或更高版本才支持。 node下载地址:

https://nodejs.org/zh-cn/

2. 创建项目

2.1 创建test目录,进入目录执行npm init
2.2 安装 puppeteer
yarn add puppeteer 或者 npm i puppeteer

可能会出现以下报错:

ERROR: Failed to download Chromium r515411! Set "PUPPETEER_SKIP_CHROMIUM_DOWNLOA
D" env variable to skip download.

是因为在执行安装的过程中需要执行install.js,这里会下载Chromium,官网建议是进行跳过,我们可以执行 —ignore-scripts 忽略这个js执行。也可以通过设置环境变量set PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1阻止下载 Chromium (因为封网,直接下载会失败)

npm i --save puppeteer --ignore-scripts
2.3 手动下载Chromium (打开蓝灯翻墙软件...)

下载地址:

https://download-chromium.appspot.com/ 

把下载刚刚下载的文件解压出来会有chrome-win32文件夹,把里面的文件拷贝到项目新建的chromium文件夹中

2.4 新建index.js(截图功能), 代码如下:
const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://y.qq.com');
    await page.screenshot({path: 'yqq.png'});
    browser.close();
})();

打开cmd执行index.js

node index.js

这时候可能出现以下错误:

(node:8672) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejec
tion id: 1): AssertionError [ERR_ASSERTION]: Chromium revision is not downloaded
. Run "npm install"
(node:8672) [DEP0018] DeprecationWarning: Unhandled promise rejections are depre
cated. In the future, promise rejections that are not handled will terminate the
 Node.js process with a non-zero exit code.

显示chromium 未下载错误,因为chromium默认的下载路径是在node_modules/puppeteer/.local-chromium/目录,这时候我们的chromium是在项目根目录,所以需要配置指定路径,修改index.js文件():

const puppeteer = require('puppeteer');

(async () => {
      const browser = await puppeteer.launch({
        executablePath: './chromium/chrome.exe',
        headless: false
      });
      const page = await browser.newPage();
      await page.goto('http://music.163.com/');
      await page.screenshot({path: 'music.png'});
      browser.close();
})();

puppeteer launch参数说明:

  • executablePath: 运行Chromium或Chrome可执行文件的路径
  • headless: 是否运行在浏览器headless模式,true为不打开浏览器执行,默认为true
  • timeout: 等待浏览器实例启动的最长时间(以毫秒为单位)。默认为30000(30秒)。通过0禁用超时
  • args: 传递给浏览器实例的其他参数

更多参数请参照官网,再次执行index.js可能出现以下错误:

Error: Protocol error (Page.getFrameTree): 'Page.getFrameTree' wasn 't found undefined

刚开始找了好久没找到答案,然后上万能的Google找了下,发现了类似的问题
image.png

可能是chromium的版本存在差异,然后重新在chromium官网下载最新版本解压到项目(要注意下相应系统chromium)

chromium官网:https://download-chromium.appspot.com/

执行index.js, 脚本运行chromium浏览器跳转到界面,截图保存到项目中,这样就成功了...

2.5 puppeteer相关地址

puppeteer神器官方文档,可以进行其他强大的功能开发..

puppeteer神器官方文档:https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-browser
查看原文

赞 16 收藏 15 评论 7

twogoods 赞了回答 · 2018-01-04

MySQL的视图查询不走索引吗?

因为在mysql中有些查询语句是用不到索引的。
1.like语句;
2.列类型为字符串类型,查询时没有用单引号引起来;
3.在where查询语句中使用表达式;
4.在where查询语句中对字段进行NULL值判断;
5.在where查询中使用了or关键字, myisam表能用到索引, innodb不行;(用UNION替换OR,可以使用索引);
6.全表扫描快于索引扫描(数据量小时)

兄弟,觉得OK的话点个赞或者采纳一下。

关注 2 回答 1

twogoods 关注了问题 · 2017-12-27

解决《高性能mysql》中某一节看不懂的部分希望大家能够给点意见

个人理解能力有限, 这本书看的总是感觉很多话理解不了

图中, 大部分时间花在数据拷贝上 什么意思啊? 然后什么缓存负载非常重要? 什么啊? 蒙了!

我看了半天认为 在查询字段有主键的时候, 二级索引可以做到覆盖到主键, 但图中第二句还是看不懂在说什么, 怎么就二次查询了??
clipboard.png

这句话感觉就是从天而降的一句话, 完全看不懂, 什么条件就为假了 看的晕乎?
clipboard.png

关注 10 回答 3

twogoods 关注了问题 · 2017-12-27

解决《高性能mysql》聚簇索引问题

如下红色框中的两句话是什么意思?
之前在看多列索引的时候, 理解的是索引创建后, 会将每条记录中对应列的值也保存到索引树中, 如果查询的字段正好都被覆盖到, 那都不用去回表了!

所以我的理解是索引树中是有真正数据的, 那下面红色框中的意思莫非是:“聚簇索引会在索引树中将所有数据放进去”?

clipboard.png

关注 8 回答 4

twogoods 发布了文章 · 2017-12-07

一款基于Mybatis的编译期SQL生成器

介绍

TgDao是一款基于Mybatis的编译期SQL生成器,利用注解来表达SQL,能根据你的方法签名生成对应的Mapper.xml文件。
它能减少你日常开发中大量简单SQL的编写,由于它只是生成Mapper.xml文件,因此对于复杂的查询场景,
你同样可以自己编写来完成一些工具所无法生成的SQL。

@Table(name = "T_User")
public class User {
    @Id("id")
    private int id;
    private String username;
    private int age;
}

上面的model定义了模型和数据库表的关系,看到下面这些方法的签名,聪明的你肯定能猜出每个方法的sql吧,这就是这个库要做的工作。

@DaoGen(model = User.class)
public interface UserDao {
    @Select
    @OrderBy("id desc")
    List<User> queryUser(@Condition(criterion = Criterions.EQUAL, column = "username") String name,
                         @Condition(criterion = Criterions.GREATER, attach = Attach.OR) int age,
                         @Limit int limit, @OffSet int offset);

    @Select
    List<User> queryUser2(@Condition(criterion = Criterions.GREATER, column = "age") int min,
                          @Condition(criterion = Criterions.LESS, column = "age") int max);

    @Select
    List<User> queryUser3(@Condition(criterion = Criterions.EQUAL, column = "username") String name,
                          @Condition(attach = Attach.OR, column = "id", criterion = Criterions.IN) String[] ids);

    @Insert(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    @BatchInsert(columns = "username,age")
    int batchInsert(List<User> users);

    @Update
    @ModelConditions({
            @ModelCondition(field = "id")
    })
    int update(User user);

    @Delete
    int delete(@Condition(criterion = Criterions.GREATER, column = "age") int min,
               @Condition(criterion = Criterions.LESS, column = "age") int max);
}

项目地址 https://github.com/twogoods/TgDao


文档

引入如下依赖:

<dependency>
  <groupId>com.github.twogoods</groupId>
  <artifactId>tgdao-core</artifactId>
  <version>0.1.3</version>
</dependency>

Table与Model关联

@Table记录数据表的名字
@Id记录主键信息
@Column映射了表字段和属性的关系,如果表字段和类属性同名,那么可以省略这个注解
@Ingore忽略这个类属性,没有哪个表字段与它关联

@Table(name = "T_User")
public class User {
    @Id("id")
    private int id;

    private String username;
    private String password;
    private int age;

    @Column("old_address")
    private String oldAddress;
    @Column("now_address")
    private String nowAddress;

    private int state;

    @Column("created_at")
    private Timestamp createdAt;
    @Column("updated_at")
    private Timestamp updatedAt;

    @Ignore
    private String remrk;

查询

@Select
@OrderBy("id desc")
List<User> queryUser(@Condition(criterion = Criterions.EQUAL, column = "username") String name,
                    @Condition(criterion = Criterions.GREATER, attach = Attach.OR) int age,
                    @Condition(column = "id", criterion = Criterions.IN) String[] ids,
                    @Limit int limit, @OffSet int offset);
@Select
  • columns:默认 select *可以配置columns("username,age")选择部分字段;
  • SqlMode:有两个选择,SqlMode.SELECTIVE 和 SqlMode.COMMON,区别是selective会检查查询条件的字段是否为null来实现动态的查询,

<if test="name != null">username = #{name}</if>

@Condition
  • criterion:查询条件,=,<,>,in等,具体见Criterions
  • column:与表字段的对应,若与字段名相同可不配置
  • attach:连接 and,or, 默认是and
  • test:selective下的判断表达式,即<if test="username != null">里的test属性

@Limit@OffSet为分页字段。
方法的参数不加任何注解一样会被当做查询条件,如下面两个函数效果是一样的:

@Select()
List<User> queryUser(Integer age);

@Select()
List<User> queryUser(@Condition(criterion = Criterions.EQUAL, column = "age") Integer age);

查询Model

上面的例子在查询条件比较多时方法参数会比较多,我们可以把查询条件封装到一个类里,使用@ModelConditions来注解查询条件,注意被@ModelConditions只能有一个参数。

@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),
       @ModelCondition(field = "idArr", column = "id", criterion = Criterions.IN, paramType = InType.ARRAY)
})
List<User> queryUser5(UserSearch userSearch);
@ModelCondition
  • field:必填,查询条件中类对应的属性
  • column:对应的表字段
  • paramType:in 查询下才需要配置,数组为array,List为collection类型
  • test:selective下的判断表达式,即<if test="username != null">里的test属性

@Page只能用在ModelConditions下的查询,并且方法参数的那个类应该有offsetlimit这两个属性。

注:

@Select(columns = "username,age")
List<User> queryUser(Integer age);

@Select(columns = "username,age")
List<User> queryUser2param(Integer age, String username);

<select id="queryUser" resultMap="XXX">select username,age from T_User
    <where>
      <if test="age != null">AND age = #{age}</if>
    </where>
</select>

<select id="queryUser2param" resultMap="XXX">select username,age from T_User
    <where>
      <if test="age != null">AND age = #{age}</if>
      <if test="username != null">AND username = #{username}</if>
    </where>
</select>

两个函数生成的sql如上,@Select的属性SqlMode默认是Selective,所以两个都有<if>条件判断,但是这里第一个函数的sql,
Mybatis不支持,执行会报错,类似no age getter in java.lang.Interger,Mybatis会把这唯一的一个参数当做对象来取里面的值。
解决方法:函数签名里强加@Param()注解,或者@Select里使用sqlMode = SqlMode.COMMON去掉生成sql里的if判断。
这个问题只会在方法只有一个参数的情况下发生,第二个函数生成的sql是ok的。

分页

查询参数里@Limit@OffSet或查询model里@Page的分页功能都比较原始,TgDao只是一款SQL生成器而已,因此你可以使用各种插件,
或者与其他框架集成。对于分页,可以无缝与PageHelper整合。

@Select
List<User> queryUser2(@Condition(criterion = Criterions.GREATER, column = "age") int min,
                @Condition(criterion = Criterions.LESS, column = "age") int max);


@Test
public void testQueryUser2() throws Exception {
   PageHelper.offsetPage(1, 10);
   List<User> users = mapper.queryUser2(12, 30);
   PageInfo page = new PageInfo<>(users);
   System.out.println(page.getTotal());
   Assert.assertTrue(page.getList().size() > 0);
}

插入

@Insert(useGeneratedKeys = true, keyProperty = "id")//获取自增id
int insert(User user);

@BatchInsert(columns = "username,age")//插入的列
int batchInsert(List<User> users);

BatchInsert强烈建议写columns,因为生成的语句并不会过滤null字段,数据库中插入null易报错。


更新

@Update(columns = "username,age")//选择更新某几个列
@ModelConditions({
       @ModelCondition(field = "id")
})
int update(User user);

删除

@Delete
int delete(@Condition(criterion = Criterions.GREATER, column = "age") int min,
          @Condition(criterion = Criterions.LESS, column = "age") int max);

@Delete
@ModelConditions({
       @ModelCondition(attach = Attach.AND, field = "minAge", column = "age", criterion = Criterions.GREATER),
       @ModelCondition(attach = Attach.AND, field = "maxAge", column = "age", criterion = Criterions.LESS)
})
int delete2(UserSearch userSearch);

selective

@Select@Count@Update@Delete都有selective这个属性,这个属性有两个值,分别是SqlMode.COMMONSqlMode.SELECTIVE
它们的区别在下面这段生成的xml里显示的很清楚,SqlMode.SELECTIVE引入了Mybatis的动态SQL能力。

  <!-- SELECTIVE -->
  <select id="queryUser" resultMap="BaseResultMap">select username,age from T_User 
    <where>
      <if test="name!=null and name!=''">AND username = #{name}</if>
      <if test="age != null">OR age = #{age}</if>
    </where>
  </select>
  
  <!-- COMMON -->
  <select id="queryUser" resultMap="BaseResultMap">select username,age from T_User 
    <where>
      AND username = #{name} OR age = #{age}
    </where>
  </select>

@Select@Count默认的selective属性是SqlMode.SELECTIVE,这样查询语句可以充分利用Mybatis的动态SQL能力。
@Update@Delete默认是SqlMode.COMMON,这样做的原因是:selective模式下如果参数全是null会使得where语句里没有任何条件,
最终变成全表的更新和删除,这是一个极其危险的动作。所以@Update@Delete慎用SqlMode.SELECTIVE模式。

@Params

在介绍这个注解时要先介绍一下Mybatis自己的@Param注解,@Param注解在方法的参数上,给参数定义了一个名字,
这样可以在xml的sql里使用这个名字来取得参数所对应的值。如下:

    List<User> queryUser(@Param("name") String name);
    
    <select id="queryUser">select * from T_User where username=#{name} </select>

明明参数就叫name,为什么还要@Param注解一个名字name呢?这是因为Java编译完,会丢掉参数名,以至于运行期mybatis不知道这个参数叫什么,所以需要注解一个名字。
在运行时看到mybatis报错如:Parameter 'XXX' not found. Available parameters are... 这就是没有这个注解导致的问题。
但是在Java8里我们已经可以通过给javac 添加-parameters参数来保留参数名字信息,这样mybatis会利用这个信息,这样就不需要加@Param注解了。
maven可以通过如下方式设置:

  <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
          <compilerArgs>
              <arg>-parameters</arg>
          </compilerArgs>
      </configuration>
  </plugin>

然而有一种情况-parameters也无能为力,List<User> queryUser4(List ids);当参数是collection或者数组类型时,mybatis依旧无法认出ids这个参数,只认collectionarray
@Params注解是Mybatis自身注解@Param-parameters外的另外一种解决方案。@Params可以注解在类和方法上,
被它注解的类和方法会在编译期自动给所有方法参数加上@Param注解,它借鉴了lombok的方式在编译期修改抽象语法树从而改变编译生成的字节码文件。

    @Select(columns = "username,age")
    @Params
    List<User> queryUser(Integer age, String username);
    
    //编译后
    List<User> queryUser(@Param("age") Integer var1, @Param("username") String var2);

更多请看example


说明

  • 编译生成的XML文件与Mapper接口在同一个包下
  • 只支持Java8和MySql
  • 修改了源代码中方法的定义或者model里和数据表的映射关系,发现编译出来的xml却没有改变,这是增量编译的原因。生成一个xml同时需要model和mapper interface两个部分,

如果你只修改了其中一个的代码,那么另一个未修改的代码编译器就不做处理,这样这一次编译就无法得到全部的信息,所以TgDao无法生成最新版本的xml。
解决方法是每次mvn clean compile先清除一下编译目录,更好的方案正在寻找...

查看原文

赞 1 收藏 6 评论 1

twogoods 回答了问题 · 2017-10-10

Mybatis: mapper.xml文件里可以取到application.properties里的值吗?

你把这个sql写成一个函数,database_name 作为参数传入就行了,database_name 可以使用@Value注解拿到

关注 3 回答 2

twogoods 分享了头条 · 2017-09-11

带你进入 java 里的异步世界

赞 1 收藏 11 评论 0

twogoods 提出了问题 · 2017-09-05

nodejs 关于并发安全的问题

nodejs里有锁的概念吗?比如扣库存这个场景,库存只有1了,两个用户同时请求,如何保证只有一个成功?

关注 5 回答 3

认证与成就

  • 获得 18 次点赞
  • 获得 128 枚徽章 获得 5 枚金徽章, 获得 47 枚银徽章, 获得 76 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-04-15
个人主页被 926 人浏览