008,Mybatis映射器&注解

映射器&注解

V哥官网:http://www.vgxit.com
本文对应视频教程:http://www.vgxit.com/course/22


1,概述:

映射器是Mybatis开发时候最复杂的部分,也是最核心的部分,它由一个接口和一个文件组成。就比如我们之前用到过的UserMapper接口和UserMapper.xml文件。这里老师说一下,其实在我们开发中,我们大量使用的还是通过XML文件的方式来编写SQL,基本上很少使用注解。

因为,我们在开发的时候,实际情况需要大量的使用动态sql技术,或者我们还需要自定义ResultMap。如果我们使用注解的方式来实现是非常麻烦的,非常不好维护。但是我们使用xml的方式就非常的直观。所以说同学们要知道以后工作中尽量的使用xml的方式。


2,Select标签和注解:

1,Select标签的用法:

<!--
    select标签,对应的是一条基于查询的sql语句
    1,id:必须和我们Mapper接口里面对应的方法名一模一样
    2,parameterType:对应的方法的参数的类型,这里可以使用全限定名也可以使用别名
    3,resultType:返回值的类型,这里可以是别名也可以是全限定名
    4,timeout:超时时间,单位是秒,默认情况下使用数据库厂商提供的JDBC驱动设置的秒数
    5,statementType:告诉mybatis使用jdbc的那个Statment工作,默认使用的PERPARED(PreparedStatment),我们也可以设置为STATEMENT(Statment)
    -->
    <select id="getUserById" parameterType="int" resultType="user">
        select * from user where id=#{id}
    </select>

2,Select注解的用法:

@Select("select * from user where id=#{id}")
    @Options(timeout = 30, statementType = StatementType.PREPARED)//对应的属性我们可以在Options注解里面定义
    User getUserById(int id);

3,自动映射和驼峰映射(重点):

V哥给大家说明一下,这一块的知识,你在一些书上看的,或者你去现在的官方文档上看的,可能有一些问题。V哥在讲课的时候发现了这个问题。然后我通过自己的实验以及跟踪源代码发现了这个问题,然后讲课的时候会给大家讲出来。

我们先思考一个问题,就是我们从数据库里面查询出来了数据,他是怎么样赋值给我们的PO对象的。mybatis会根据字段的名字和我们的PO属性的名字来进行自动映射。就是如果我们数据库的字段名和我们的PO的属性名完全匹配,这个时候,他就会映射。

其实我们现在并不是这样映射的。我们现在可以看到,如果我们把某个字段名字改来和数据库字段名字不一样,还是映射成功了。

如果我们用如下sql再来看看:

select name,id,age,gender from user where id=#{id}

这个sql我们只是把查询的字段的顺序修改了一下,但是名字还是能够对应上,但是自动映射就出问题了。老师告诉一下大家就是lombok的原因。

我们的User的PO对应的代码加上了@Builder注解。我们再来看一下User被lombok编译的时候生成的代码,它之后一个构造方法,如下:

User(Integer id, String name, Short gender, Integer age) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

这个mybatis在构造对象的时候,没有找到无参的构造方法,它就会调用有参构造方法来实现。这个时候他会按照字段查询出来的顺序来填充有参构造方法的参数。如果我们要解决怎么办?

我们可以加上两个注解来解决:@AllArgsConstructor,@NoArgsConstructor。这个时候,User这个PO就会自动生成一个无参的构造方法,一个有参的构造方法。如果Mybatis发现一个对象拥有无参构造方法的时候,它会首先调用无参构造方法创建对象,然后调用set方法来设置对应的属性。

我们设置开启和关闭自动映射可以在mybatis-config.xml中的setting元素中配置autoMappingBehavior就可以了,而autoMappingBehavior对应值可以是如下:

  • NONE: 不进行自动映射,如果设置为了NONE,我们映射数据库字段和PO的属性,必须使用ResultMap。
  • PARTIAL: 默认值,只是对没有嵌套结果集自动映射
  • FULL: 对所有结果进集进行自动映射,一般情况下,如果有结果集的,我们也是使用ResultMap,所以FULL真的是很少使用。

一般情况下,我们使用默认值就好了。

但是在一般情况下,我们可能有如下的情况:

比如,我们在数据库里面加上一个字段叫做nick_name,但是在PO里面我们对应的属性是nickName:

/**
     * 昵称
     */
    private String nickName;

这个时候,我们再来运行,发现nickName=null,这个是因为我们数据库字段是nick_name,而属性是nickName。两个名字根本就对应不上,所以说造成了没有办法映射。这个时候怎么办?

方法一:

select id,name,gender,age,nick_name as nickName from user where id=#{id}

我们通过别名的方式,来给对应的字段加上别名,这个别名和我们的PO的属性保持一致。

方法二:

开启驼峰映射规则。如果我们发现数据库字段和PO属性能够完全对应上的,那么就映射。如果对应不上,那么Mybatis会自动把下划线转化为驼峰,再来看看有没有对应上的。比如nick_name,mybatis会先变成nickName然后再来映射。

这个时候,我们可以通过一个全局配置来开启驼峰映射:

<!--开启驼峰映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

4,多参数传递:

比如我们现在有一个需求,我们要按照性别和年龄来查询匹配条件的第一个用户数据。有的同学可能想到了用如下的方法:

1,首先在Mapper中定义一个方法:

User getUserByGenderAndAge(short gender, int age);

对应的sql如下:

<select id="getUserByGenderAndAge" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
    select * from user where gender=#{gender} and age=#{age} limit 1
</select>

这个是因为,如果我们只有一个参数的时候,我们在xml中配置sql,我们随便写一个#{xxx}来占位就可以了。但是,如果有多个参数的时候,mybatis就不知道你的哪个参数对应的是xml中哪一个变量。这个我们可以使用顺序参数来解决:

<select id="getUserByGenderAndAge" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
        select * from user where gender=#{param1} and age=#{param2} limit 1
    </select>

2,我们可以使用map接口来做(实际开发使用不多):

首先我们定义对应的Mapper的方法:

User getUserByGenderAndAge(Map<String, Object> param);

然后,我们编写对应的xml:

<select id="getUserByGenderAndAge" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
        select * from user where gender=#{gender} and age=#{age} limit 1
    </select>

具体调用:

private static void getUserByGenderAndAge() throws IOException {
        try (SqlSession sqlSession = MybatisTool.getSqlSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            Map<String, Object> param = new HashMap<>();
            param.put("gender", (short) 2);
            param.put("age", 28);
            User user = userMapper.getUserByGenderAndAge(param);
            System.out.println(user);
        }
    }

3,采用QO的方式来做:

QO的方式还是非常重要的,如果我们的查询条件太多,一般情况下,都会来使用的方式。

首先,我们定义一个专门用来封装查询参数的对象,我们叫做QO:

package com.vgxit.learn.vgmybatis.ktdm.qo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserQO {
    private Short gender;
    private Integer age;
}

然后我们定义对应的Mapper的方法:

User getUserByGenderAndAge(UserQO userQO);

对应的xml里面的sql写法和Map方式传递参数一样。

然后具体调用的代码:

private static void getUserByGenderAndAge() throws IOException {
        try (SqlSession sqlSession = MybatisTool.getSqlSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            UserQO userQO = UserQO.builder()
                    .gender((short) 2)
                    .age(28)
                    .build();
            User user = userMapper.getUserByGenderAndAge(userQO);
            System.out.println(user);
        }
    }

4,注解的方式来传递多个参数:

如果我们传递条件参数不是过多的情况下,用这种多参数传递的方式还是挺多的。首先编写一个Mapper对应的方法:

User getUserByGenderAndAge(@Param("gender") short gender, @Param("age") int age);

然后,对应的xml里面的参数和我们用QO和Map一样:

try (SqlSession sqlSession = MybatisTool.getSqlSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.getUserByGenderAndAge((short) 2, 28);
            System.out.println(user);
        }

4,分页参数RowBounds(了解):

我们在开发的时候,往往需要分页的获取数据。mybatis内置了一个分页参数对象叫做RowBounds。

1,创建对应的Mapper方法,通过性别分页获取数据:

/**
     * 通过性别来获取用户数据
     * @param gender
     * @return
     */
    List<User> findUserByGender(short gender, RowBounds rowBounds);

2,对应的xml:

<select id="findUserByGender" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
        select * from user where gender=#{gender}
    </select>

3,对应的调用方法:

try (SqlSession sqlSession = MybatisTool.getSqlSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            RowBounds rowBounds = new RowBounds(0, 3);
            List<User> users = userMapper.findUserByGender((short) 1, rowBounds);
            users.forEach(System.out::println);
        }

注意:分页的逻辑是mybatis在运行的时候自动加上去的,我们做开发的时候,对应的sql我们不用再来写分页的相关代码了。但是,同学们千万别用这个方法,因为使用RowBounds的原理是把满足条件的所有数据全部查询出来,然后Mybatis在内存中筛选出对应的数据。


3,insert标签和注解:

insert标签和注解,顾名思义,就是插入数据。

下面我们分别用xml和注解的方式插入一个用户:

xml方式:

<insert id="addUser" parameterType="user">
    insert into user values (null, #{name}, #{gender}, #{age}, #{nickName})
</insert>

注解方式:

@Insert("insert into user values (null, #{name}, #{gender}, #{age}, #{nickName})")
int addUser(User user);

具体调用:

SqlSessionFactory sessionFactory = MybatisTool.getSqlSessionFactory();
try (SqlSession sqlSession = sessionFactory.openSession()) {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setName("周杰伦");
    user.setNickName("Jay");
    user.setGender((short) 1);
    user.setAge(40);
    userMapper.addUser(user);
    sqlSession.commit();
}

insert获取主键

我们通过面上的sql语句,无论是xml还是注解,我们添加的时候都会把id这一列设置为null。因为mysql建表的时候,我们设置了主键自增。但是,我们有时候还是需要继续使用这个主键,比如我们有时候需要把该数据的id和其他表里面的数据关联起来。这个时候怎么做?

(1),XML主键回填的方式一:

<!--
    useGeneratedKeys="true" 表示开启主键回填功能,就是插入数据之后,把新插入的数据的主键Id设置到对应的PO的某个字段中
    keyProperty="id" 如果主键回填功能开启,这个属性指定回填到PO中的哪个属性中
    -->
    <insert id="addUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
        insert into user values (null, #{name}, #{gender}, #{age}, #{nickName});
    </insert>

运行结果如下:

(2),查询刚刚生成主键:

<insert id="addUser" parameterType="user">
        <selectKey keyProperty="id" resultType="int">
            select last_insert_id();
        </selectKey>
        insert into user values (null, #{name}, #{gender}, #{age}, #{nickName});
    </insert>

上面这种方式,我们插入了数据之后,查询最后一次被插入的主键id。然后通过设置selectKey的keyProperty对应字段。

对应的操作结果:

(3),注解的方式实现主键回填:

@Insert("insert into user values (null, #{name}, #{gender}, #{age}, #{nickName});")
@Options(useGeneratedKeys = true, keyProperty = "id")
int addUser(User user);

(4),注解的方式查询刚刚插入的主键:

@Insert("insert into user values (null, #{name}, #{gender}, #{age}, #{nickName});")
    @SelectKey(statement = "select last_insert_id()", keyProperty = "id", resultType = Integer.class, before = false)
    int addUser(User user);

4,update,delete标签和注解:

这两个之前都讲过了,这里提出来一下就好了


5,Sql元素

sql元素的作用在于可以定义SQL部分,方便后面的SQL引用它。比如最经典的列名。如果我们的的表的字段非常多。那么这些字段在多个sql里面重复编写还是很麻烦的。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//rnybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vgxit.learn.vgmybatis.ktdm.mapper.UserMapper">
    <sql id="dataFields">
        name, gender, age, nick_name
    </sql>
    <sql id="allFields">
        id, <include refid="dataFields"/>
    </sql>

    <insert id="addUser" parameterType="user">
        <selectKey keyProperty="id" resultType="int">
            select last_insert_id();
        </selectKey>
        insert into user (<include refid="dataFields"/>) values (#{name}, #{gender}, #{age}, #{nickName});
    </insert>
    <update id="updateUser" parameterType="user">
        update user set name=#{name}, gender=#{gender}, age=#{age} where id=#{id}
    </update>
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>
    <!--
    select标签,对应的是一条基于查询的sql语句
    1,id:必须和我们Mapper接口里面对应的方法名一模一样
    2,parameterType:对应的方法的参数的类型,这里可以使用全限定名也可以使用别名
    3,resultType:返回值的类型,这里可以是别名也可以是全限定名
    4,timeout:超时时间,单位是秒,默认情况下使用数据库厂商提供的JDBC驱动设置的秒数
    5,statementType:告诉mybatis使用jdbc的那个Statment工作,默认使用的PERPARED(PreparedStatment),我们也可以设置为STATEMENT(Statment)
    -->
    <select id="getUserById" parameterType="int" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
        select <include refid="allFields"/> from user where id=#{id}
    </select>
    <select id="getUserByGenderAndAge" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
        select <include refid="allFields"/> from user where gender=#{gender} and age=#{age} limit 1
    </select>
    <select id="findUserByGender" resultType="com.vgxit.learn.vgmybatis.ktdm.po.User">
        select <include refid="allFields"/> from user where gender=#{gender}
    </select>
</mapper>

就是我们有些sql太复杂了,但是这些复杂的sql中有很多片段是一模一样的,那么我们可以把这些片段提取出来,然后放在sql标签里面,我们要用的时候,使用include标签来引入。注解的方式,我们就不讲了,注解的方式没有sql和include的功能。但是注解的方式天然就不需要这种功能。我们可以用如下代码操作:

package com.vgxit.learn.vgmybatis.ktdm.mapper;

import com.vgxit.learn.vgmybatis.ktdm.po.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.StatementType;

/**
 * 基于Annotation的User的Mapper
 */
public interface UserAnnMapper {
    static final String DATA_FILES = "name, gender, age, nick_name";
    static final String ALL_FIELS = "id, " + DATA_FILES;

    @Select("select " + ALL_FIELS + " from user where id=#{id}")
    @Options(timeout = 30, statementType = StatementType.PREPARED)//对应的属性我们可以在Options注解里面定义
    User getUserById(int id);

    @Insert("insert into user(" + DATA_FILES + ") values (#{name}, #{gender}, #{age}, #{nickName});")
    @SelectKey(statement = "select last_insert_id()", keyProperty = "id", resultType = Integer.class, before = false)
    int addUser(User user);

    @Update("update user set name=#{name}, gender=#{gender}, age=#{age} where id=#{id}")
    int updateUser(User user);

    @Delete("delete from user where id=#{id}")
    int deleteUser(int id);
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容