映射器&注解
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);
}