飘易博客(作者:Flymorn)
订阅《飘易博客》RSS,第一时间查看最新文章!
飘易首页 | 留言本 | 关于我 | 订阅Feed

mybatis新模式MyBatis3DynamicSQL的使用及join连接查询

Author:飘易 Source:飘易
Categories:JAVA开发 PostTime:2019-12-25 17:22:07
正 文:

MyBatis Generator 1.3.6版本开始支持新的一种runtime模式:MyBatis3DynamicSql。这是一种全新的模式,对java的版本要求最低是java 8。


该模式下不再生成 XML,不再生成 Example 类。文档里也推荐:join 操作要用到的 resultMap 应该是 XML 文件中的唯一元素。


MyBatis Generator (>= 1.3.6) 也已经提供支持,只需要将 context 的 targetRuntime 属性更改为 MyBatis3DynamicSQL 即可生成新的动态 SQL。


牛逼在哪里?它可构造任意 SQL

SelectStatementProvider selectStatement = select(id, firstName, lastName, birthDate, employed, occupation)
    .from(employee)
    .where(firstName, isEqualTo("Bob"), or(firstName, isEqualTo("Alice")))
    .build()
    .render(RenderingStrategy.MYBATIS3);
List<Employee> employees = mapper.selectMany(selectStatement);


MyBatis Generator 的配置文件里这样配置:

<generatorConfiguration>
  <context id="dsql" targetRuntime="MyBatis3DynamicSql">
    <jdbcConnection driverClass="org.hsqldb.jdbcDriver"
        connectionURL="jdbc:hsqldb:mem:aname" />

    <javaModelGenerator targetPackage="example.model" targetProject="src/main/java"/>

    <javaClientGenerator targetPackage="example.mapper" targetProject="src/main/java"/>

    <table tableName="FooTable" />
  </context>
</generatorConfiguration>


然后Run mybatis-generator:generate  就可以生成以下的三个类了:

model类、dao层的mapper类、*DynamicSqlSupport动态语句支持类。


本文的关注点是怎么在这种新模式下MyBatis3DynamicSql,使用关联查询, 就是对应老模式 MyBatis3 下的 association(一对一)、collection(一对多)。


依然拿下面的模型举例:

新闻表 news (其中字段cat_id对应分类表的id)

新闻分类表 news_categories


News.java 模型里添加:

    /* 自定义属性 */
    private NewsCategories cat;
    public NewsCategories getCat(){ return cat; }
    public void setCat(NewsCategories cat){ this.cat = cat; }
    /* 自定义属性 - 部分字段 */
    private Map catMap;
    public Map getCatMap(){ return catMap; }
    public void setCatMap(Map catMap){ this.catMap = catMap; }

NewsCategories.java 模型里添加:

    // 自定义
    private List<News> newsList;
    public List<News> getNewsList(){ return newsList; }
    public void setNewsList(List<News> newsList){ this.newsList = newsList; }
    // 自定义 - Map 部分字段
    private List<Map> newsMapList;
    public List<Map> getNewsMapList(){ return newsMapList; }
    public void setNewsMapList(List<Map> newsMapList){ this.newsMapList = newsMapList; }


模型里为什么定义2种形式呢?飘易是为了演示在某些场景下,只需要返回关联模型的一部分字段,比如有上百个字段但只需要几个字段,要去除部分敏感字段如密码等。定义为Map形式支持返回部分字段,而直接使用模型的话,即时对应字段没有传值,也会返回 null。


NewsMapper.java DAO层添加:

    // 自定义
    @Generated(value="org.mybatis.generator.api.MyBatisGenerator", comments="Source Table: news")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("joinNewsResult")
    Optional<News> selectOneJoin(SelectStatementProvider selectStatement);
    // 自定义
    @Generated(value="org.mybatis.generator.api.MyBatisGenerator", comments="Source Table: news")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("joinNewsResult")
    List<News> selectManyJoin(SelectStatementProvider selectStatement);


NewsCategoriesMapper.java Dao添加:

    // 自定义 - join 连接查询
    @Generated(value="org.mybatis.generator.api.MyBatisGenerator", comments="Source Table: news_categories")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("joinNewsCategoriesResult")
    Optional<NewsCategories> selectOneJoin(SelectStatementProvider selectStatement);
    // 自定义 - join 连接查询
    @Generated(value="org.mybatis.generator.api.MyBatisGenerator", comments="Source Table: news_categories")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("joinNewsCategoriesResult")
    List<NewsCategories> selectManyJoin(SelectStatementProvider selectStatement);


注意,使用join查询时依然需要配置xml文件,因为纯粹的注解方式并不能很好的定义关联关系 association(一对一)、collection(一对多);但是xml文件里只需要定义 resultMap 节点,不需要定义其他任何节点了。


NewsMapper.xml 里定义:

<mapper namespace="com.example.demo.mapper.NewsMapper">
    <resultMap id="joinNewsResult" type="com.example.demo.model.News">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="cat_id" jdbcType="INTEGER" property="catId" />
        <result column="title" jdbcType="VARCHAR" property="title" />
        <result column="keywords" jdbcType="VARCHAR" property="keywords" />
        <result column="description" jdbcType="VARCHAR" property="description" />
        <result column="filepaths" jdbcType="VARCHAR" property="filepaths" />
        <result column="views" jdbcType="INTEGER" property="views" />
        <result column="recommend" jdbcType="TINYINT" property="recommend" />
        <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
        <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
        <result column="content" jdbcType="LONGVARCHAR" property="content" />
        <!--  一对一关联关系  -->
        <association property="cat" javaType="com.example.demo.model.NewsCategories">
            <id column="c_id" jdbcType="INTEGER" property="id"/>
            <result column="c_title" jdbcType="VARCHAR" property="title"/>
        </association>
        <association property="catMap" javaType="map">
            <id column="c_id" jdbcType="INTEGER" property="id"/>
            <result column="c_title" jdbcType="VARCHAR" property="title"/>
        </association>
    </resultMap>
</mapper>


NewsCategoriesMapper.xml 文件里定义:

<mapper namespace="com.example.demo.mapper.NewsCategoriesMapper">
    <resultMap id="joinNewsCategoriesResult" type="com.example.demo.model.NewsCategories">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="title" jdbcType="VARCHAR" property="title" />
        <result column="keywords" jdbcType="VARCHAR" property="keywords" />
        <result column="description" jdbcType="VARCHAR" property="description" />
        <result column="sort_id" jdbcType="INTEGER" property="sortId" />
        <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
        <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
        <!--  一对多关联关系  -->
        <collection property="newsList" ofType="com.example.demo.model.News">
            <id column="n_id" jdbcType="INTEGER" property="id" />
            <result column="cat_id" jdbcType="INTEGER" property="catId" />
            <result column="n_title" jdbcType="VARCHAR" property="title" />
            <result column="keywords" jdbcType="VARCHAR" property="keywords" />
            <result column="description" jdbcType="VARCHAR" property="description" />
            <result column="filepaths" jdbcType="VARCHAR" property="filepaths" />
            <result column="views" jdbcType="INTEGER" property="views" />
            <result column="recommend" jdbcType="TINYINT" property="recommend" />
            <result column="n_created_at" jdbcType="TIMESTAMP" property="createdAt" />
            <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
            <result column="content" jdbcType="LONGVARCHAR" property="content" />
        </collection>
        <collection property="newsMapList" ofType="java.util.Map">
            <id column="n_id" jdbcType="INTEGER" property="id" />
            <result column="n_title" jdbcType="VARCHAR" property="title" />
            <result column="n_created_at" jdbcType="TIMESTAMP" property="createdAt" />
        </collection>
    </resultMap>
</mapper>


注意:association(一对一)里使用的属性是 javaType;而 collection(一对多)里使用的属性是 ofType


好了,准备工作做好,下面飘易就在控制器里写控制层的代码了。


NewsController.java 里编写代码:

@RestController
public class NewsController {

    @Resource
    private NewsMapper newsMapper;
    @Resource
    private NewsCategoriesMapper newsCategoriesMapper;

    /**
     * 单个资源
     * */
    @GetMapping("/news/{id}")
    public Result item(@PathVariable("id") Integer id) {
        // 筛选
        SelectStatementProvider selectStatement = select(news.allColumns(),
                (newsCategories.id).as("c_id"), (newsCategories.title).as("c_title"))
                .from(news)
                .leftJoin(newsCategories).on(news.catId, equalTo(newsCategories.id))
                .where(news.id, isEqualTo(id))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        Optional<News> news = newsMapper.selectOneJoin(selectStatement);
        return Result.data(news);
    }

    /**
     * 资源列表
     * */
    @GetMapping("/news")
    public Result itemList(@RequestParam Map<String,String> req) {
        // 分页页码参数
        int pageNum = req.containsKey("pageNum") ? Integer.parseInt(req.get("pageNum")) : Result.pageNum;
        int pageSize = req.containsKey("pageSize") ? Integer.parseInt(req.get("pageSize")) : Result.pageSize;
        // 开始分页
        PageHelper.startPage(pageNum, pageSize);// 紧跟着的第一个select方法会被分页
        // 筛选
        SelectStatementProvider selectStatement = select(news.allColumns(),
                (newsCategories.id).as("c_id"), (newsCategories.title).as("c_title"))
                .from(news)
                .leftJoin(newsCategories).on(news.catId, equalTo(newsCategories.id))
                .orderBy(sortColumn("news.id").descending())
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<News> list = newsMapper.selectManyJoin(selectStatement);
        PageInfo pageInfo = new PageInfo(list);// 分页包装
        return Result.list(pageInfo);
    }

    /**
     * 增加
     * @param news: 接受 Content-Type: application/x-www-form-urlencoded
     *            body里为 title=test&cat_id=1
     * */
    @PostMapping("/news")
    public Result insert(News news) {
        Date date = new Date();
        news.setCreatedAt(date);
        news.setUpdatedAt(date);
        Integer res = newsMapper.insertSelective(news);
        return Result.data(res);
    }

    /**
     * 编辑
     * */
    @PutMapping("/news/{id}")
    public Result update(@PathVariable("id") Integer id, News news) {
        Date date = new Date();
        news.setUpdatedAt(date);
        news.setId(id);
        Integer res = newsMapper.updateByPrimaryKeySelective(news);
        return Result.data(res);
    }

    /**
     * 删除
     * */
    @DeleteMapping("/news/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        Integer res = newsMapper.deleteByPrimaryKey(id);
        return Result.data(res);
    }

}


NewsCategoriesController.java 里编写代码:

@RestController
public class NewsCategoryController {

    @Resource
    private NewsMapper newsMapper;
    @Resource
    private NewsCategoriesMapper newsCategoriesMapper;

    /**
     * 单个资源
     * */
    @GetMapping("/news_category/{id}")
    public Result item(@PathVariable("id") Integer id) {
        SelectStatementProvider selectStatement = select(newsCategories.allColumns(),
                (news.id).as("n_id"),(news.title).as("n_title"),(news.createdAt).as("n_created_at"))
                .from(newsCategories)
                .leftJoin(news)
                .on(newsCategories.id, equalTo(news.catId))
                .where(newsCategories.id, isEqualTo(id))
                .orderBy(sortColumn("news_categories.id").descending())
                .build()
                .render(RenderingStrategies.MYBATIS3);
        // 单个
        Optional<NewsCategories> newsCategories = newsCategoriesMapper.selectOneJoin(selectStatement);
        return Result.data(newsCategories);
    }

    /**
     * 资源列表
     * */
    @GetMapping("/news_category")
    public Result itemList(@RequestParam Map<String,String> req) {
        // 分页页码参数
        int pageNum = req.containsKey("pageNum") ? Integer.parseInt(req.get("pageNum")) : Result.pageNum;
        int pageSize = req.containsKey("pageSize") ? Integer.parseInt(req.get("pageSize")) : Result.pageSize;
        // 开始分页
        PageHelper.startPage(pageNum, pageSize);// 紧跟着的第一个select方法会被分页
        // 筛选
        SelectStatementProvider selectStatement = select(newsCategories.allColumns(),
                (news.id).as("n_id"),(news.title).as("n_title"),(news.createdAt).as("n_created_at"))
                .from(newsCategories)
                .leftJoin(news)
                .on(newsCategories.id, equalTo(news.catId))
                .orderBy(sortColumn("news_categories.id").descending())
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<NewsCategories> list = newsCategoriesMapper.selectManyJoin(selectStatement);
        PageInfo pageInfo = new PageInfo(list);// 分页包装
        return Result.list(pageInfo);
    }

    /**
     * 增加
     * @param newsCategories: 接受 Content-Type: application/x-www-form-urlencoded
     *            body里为 title=test&cat_id=1
     * */
    @PostMapping("/news_category")
    public Result insert(NewsCategories newsCategories) {
        Date date = new Date();
        newsCategories.setCreatedAt(date);
        newsCategories.setUpdatedAt(date);
        Integer res = newsCategoriesMapper.insertSelective(newsCategories);
        return Result.data(res);
    }

    /**
     * 编辑
     * */
    @PutMapping("/news_category/{id}")
    public Result update(@PathVariable("id") Integer id, NewsCategories newsCategories) {
        Date date = new Date();
        newsCategories.setUpdatedAt(date);
        newsCategories.setId(id);
        Integer res = newsCategoriesMapper.updateByPrimaryKeySelective(newsCategories);
        return Result.data(res);
    }

    /**
     * 删除
     * */
    @DeleteMapping("/news_category/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        Integer res = newsCategoriesMapper.deleteByPrimaryKey(id);
        return Result.data(res);
    }
}


关联查询里,association(一对一)、collection(一对多)里 如果使用 select 属性的话,就会导致 N+1 的问题。我们使用 join 连接查询,这样可以避免产生 N+1 问题



使用postman,查询 news 单个资源:


查询 news 列表:


查询 news_categories 单个资源:

查询 news_categories 列表:



【参考】

1、MyBatis Dynamic SQL Usage Notes:https://mybatis.org/generator/generatedobjects/dynamicSqlV2.html

2、MyBatis Dynamic SQL Quick Start:https://mybatis.org/mybatis-dynamic-sql/docs/quickStart.html



作者:飘易
来源:飘易
版权所有。转载时必须以链接形式注明作者和原始出处及本声明。
上一篇:lnmp1.5使用Let'sEncrypt创建SSL证书自动续期问题
下一篇:Springboot+mybatis的association、collection的使用&只返回部分字段
0条评论 “mybatis新模式MyBatis3DynamicSQL的使用及join连接查询”
No Comment .
发表评论
名称(*必填)
邮件(选填)
网站(选填)

记住我,下次回复时不用重新输入个人信息
© 2007-2019 飘易博客 Www.Piaoyi.Org 原创文章版权由飘易所有