文章目录
- 一、Mybatis接口绑定
- 二、Mapper和对象
- 三、获取主键
- 四、动态SQL
- 五、其它相关问题
本系列文章:
Mybatis(一)Mybatis的基本使用
Mybatis(二)Mybatis的高级使用
Mybatis(三)配置文件解析流程
Mybatis(四)映射文件解析流程
Mybatis(五)SQL执行流程
Mybatis(六)数据源、缓存机制、插件机制
一、Mybatis接口绑定
1.1 接口绑定的两种实现方式
在没使用绑定接口时,需要用SqlSession来进行增删改查,示例:
student user = (student) session.selectOne("test.studentMapper.selectUserByID", 1);
接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,直接调用接口方法就可以,这样比起原来了SqlSession提供的方法,有更加灵活的选择和设置。
接口绑定有两种实现方式:
1.1.1 通过注解绑定
就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定。这种方式不常用,因为Sql和Java代码耦合性太高。示例:
//这种方式不用写mapper.xml
@Select("select * from `tb_Teacher` where id = #{id}")
public teacher selectTeacherByID(int id);
1.1.2 通过xml里面写SQL来绑定*
这种方式常用,因为耦合性低。
全局配置文件和mapper.xml文件是最基本的配置,仍然需要。不过,这次不编写dao类,直接创建一个mapper接口。示例:
package com.test.mapper;
import com.test.po.Student;
import java.util.List;
public interface StudentMapper {
List<Student> findAll();
int insert(Student student);
int delete(Integer id);
List<Student> findByName(String value);
}
mapper.xml文件(namespace必须为接口的全路径名
),示例:
<mapper namespace="com.test.mapper.StudentMapper">
<select id="findAll" resultType="com.test.po.Student">
SELECT * FROM student;
</select>
<insert id="insert" parameterType="com.test.po.Student">
INSERT INTO
student (name,score,age,gender)
VALUES
(
#{name},
#{score},
#{age},
#{gender}
);
</insert>
<delete id="delete" parameterType="int">
DELETE FROM student WHERE id = #{id};
</delete>
<select id="findByName" parameterType="string" resultType="student">
SELECT * FROM student WHERE name like '%${value}%';
</select>
</mapper>
mapper接口和mapper.xml之间需要遵循一定规则,才能成功的让mybatis将mapper接口和mapper.xml绑定起来:
- mapper接口的全限定名,要和mapper.xml的namespace属性一致;
- mapper接口中的方法名要和mapper.xml中的SQL标签的id一致;
- mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致;
- mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致。
测试代码示例:
public class MapperProxyTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.findAll();
studentList.forEach(System.out::println);
}
}
结果示例:
基于这个mapper接口,mybatis会自动找到对应的mapper.xml,然后对mapper接口使用动态代理的方式生成一个代理类。
1.2 接口绑定的相关问题
1.2.1 使用MyBatis的mapper接口调用时的4个要求
1、Mapper接口方法名
和mapper.xml中定义的每个sql的id
相同。
2、Mapper接口方法的输入参数类型
和mapper.xml中定义的每个sql 的parameterType的类型
相同。
3、Mapper接口方法的输出参数类型
和mapper.xml中定义的每个sql的resultType的类型
相同。
4、Mapper.xml文件中的namespace即是mapper接口的类路径
。 namespace示例:
<mapper namespace="com.domain.Student">
<select id="selectById" parameterType="int" resultType="student">
select * from student where id=#{id}
</select>
</mapper>
1.2.2 Mybatis中不同的xml映射文件,id是否可以重复*
不同的Xml映射文件,如果配置了namespace,那么id可以重复
;如果没有配置namespace,那么id不能重复。namespace不是必须的,只是最佳实践而已。在实际开发中一般都使用namespace。
原因:namespace+id
是作为Map<String, MappedStatement>的key
使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
xml文件中,namespace和id的示例:
<mapper namespace="com.test.dao.EmpDao">
<delete id="delete">
delete from emp where empno = #{empno}
</delete>
</mapper>
1.2.3 通常一个xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗
Dao接口,就是人们常说的Mapper接口
.接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:
com.mybatis3.mappers.StudentDao.findStudentById
可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>
标签,都会被解析为一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略
。
Dao接口的工作原理是JDK动态代理
,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
二、Mapper和对象
2.1 当实体类中的属性名和表中的字段名不一样时的处理方法*
2.1.1 自动转为驼峰式字段名*
Mybatis中有个属性map-underscore-to-camel-case
。该属性默认值为false。该属性表示数据库中的下划线格式的字段名是否可以自动转换成Java代码中的驼峰格式,比如数据库的user_name和实体类属性:userName。
这种方式严格要求数据库中的字段名和Java代码中的属性名要有对应关系,才能完成字段名转换。并且在实体类属性上也不用加@TableField注解,建议使用该方式。
- 1、application.yml配置map-underscore-to-camel-case示例
#mybatis配置
mybatis:
configuration:
map-underscore-to-camel-case: true
- 2、application.properties配置map-underscore-to-camel-case示例
mybatis.configuration.map-underscore-to-camel-case:=true
- 3、mybatis-config.xml配置map-underscore-to-camel-case示例
<configuration>
<!--开启驼峰命名规则自动转换-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
2.1.2 写SQL语句时起别名*
这种方式,实现较简单,较常用。
<select id="getEmployeeById" resultType="com.test.entities.Employee">
select id,
first_name firstName,
email,
salary,
dept_id deptID
from employees
where id = #{id}
</select>
在上面的例子中,first_name、dept_id是数据库表里的列名,firstName、deptID是实体类对应的属性名。
2.1.3 使用resultMap来自定义映射规则*
如果在多个查询sql里,返回值都是一个对象。此时如果还是在每个sql里写别名的话, 就显得重复了,可以配置一个数据库字段名和实体类字段名的映射规则,用<resultMap>
标签实现。
<select id="getEmployeeById" resultMap="myMap">
select * from employees where id = #{id}
</select>
<!-- 自定义高级映射 -->
<resultMap type="com.test.entities.Employee" id="myMap">
<!-- 映射主键 -->
<id column="id" property="id"/>
<!-- 映射其他列 -->
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
</resultMap>
在上面的例子中,last_name、dept_id是数据库中表的列名,lastName、deptId是实体类的属性名。
2.2 resultType和resultMap*
MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap.resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,resultType跟resultMap不能同时存在。
- 1、resultType
表示返回的结果的类型,此类型只能返回单一的对象(比如Integer、String等)
。
当返回的结果是一个集合的时候,并不需要resultMap,只需要使用resultType指定集合中的元素类型即可。示例:
<mapper namespace="com.domain.Student">
<select id="selectById" parameterType="int" resultType="student">
select * from student where id=#{id}
</select>
</mapper>
- 2、resultMap
当进行关联查询的时候,在返回结果的对象中还包含另一个对象的引用时,此时需要返回自定义结果集合(比如DTO对象等)
,使用resultMap,示例:
<select id="getEmployeeById" resultMap="myMap">
select * from employees where id = #{id}
</select>
<!-- 自定义高级映射 -->
<resultMap type="com.atguigu.mybatis.entities.Employee" id="myMap">
<!-- 映射主键 -->
<id column="id" property="id"/>
<!-- 映射其他列 -->
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
</resultMap>
2.3 Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?*
第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系
。
第二种是使用sql列的别名功能,将列别名书写为对象属性名
。
封装对象原理:有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的
。
2.4 在mapper中如何传递多个参数
2.4.1 顺序传参法
#{}里面的数字代表传入参数的顺序。这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
</select>
2.4.2 @Param注解传参法*
#{}里面的名称对应的是注解@Param括号里面修饰的名称。这种方法在参数不多的情况还是比较直观的,推荐使用。
//使用 @param 注解:
public interface usermapper {
user selectuser(@param("username") string username,@param("password") string password);
}
<select id="selectuser" resulttype="user">
select id, username, password
from some_table
where username = #{username}
and password = #{password}
</select>
2.4.3 Map传参法*
#{}里面的名称对应的是Map里面的key名称。这种方法适合传递多个参数,且参数易变能灵活传递
的情况。
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
2.4.4 Java Bean传参法*
#{}里面的名称对应的是User类里面的成员属性。这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便
,推荐使用。
public User selectUser(User user);
<select id="selectUser" parameterType="com.test.pojo.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
三、获取主键
3.1 代理主键和自然主键
- 自然主键是指事物属性中的自然唯一标示(例如身份证号);
- 代理主键是指与业务无关的,无意义的数字序列值;
- 在表设计时,
优先推荐代理主键
,不推荐自然主键(代理主键无意义,所以与业务解耦。另一方面,自然主键是自然界的事物,一般为字符串,处理麻烦)。
3.2 获取主键
通常会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,在插入一条记录后,有两种方式得到数据库自动生成的这条记录的主键id。
insert方法总是返回一个int值 ,这个值代表的是插入的行数。如果采用自增长策略,自动生成的键值在insert方法执行完后可以被设置到传入的参数对象中。
Sql示例:
<insert id="insertname" usegeneratedkeys="true" keyproperty="id">
insert into names (name) values (#{name})
</insert>
Java代码示例:
name name = new name();
name.setname(“fred”);
int rows = mapper.insertname(name);
// 完成后,id 已经被设置到对象中
system.out.println(“rows inserted = ” + rows);
system.out.println(“generated key value = ” + name.getid());
3.2.1 使用useGeneratedKeys和keyProperty属性
示例:
<insert id="insert" parameterType="com.test.po.Student" useGeneratedKeys="true" keyProperty="id">
INSERT INTO student (name,score,age,gender)
VALUES (
#{name},
#{score},
#{age},
#{gender}
);
</insert>
该方式适用于支持主键自增的数据库(Mysql、Sql server)。
有三个地方可以设置useGeneratedKeys=true
参数:
- 1、在setting元素中设置 useGeneratedKeys参数
在setting元素中设置的useGeneratedKeys是一个全局的参数,但是只是对接口接口映射器产生影响,对xml映射器无效。示例:
<settings>
<!--
允许JDBC支持自动生成主键,需要驱动兼容。 如果设置为true则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。
-->
<setting name="useGeneratedKeys" value="true" />
</settings>
代码中就可以获取ID了:
public interface TestMapper {
// 受全局useGeneratedKeys参数控制,添加记录之后将返回主键id
@Insert("insert into test(name,descr,url,create_time,update_time) values(#{name},#{descr},#{url},now(),now())")
Integer insertOneTest(Test test);
}
- 2、在XML映射器中配置useGeneratedKeys参数
示例:
<!-- 插入数据:返回记录的id值 -->
<insert id="insertOneTest" parameterType="org.chench.test.mybatis.model.Test" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into test(name,descr,url,create_time,update_time)
values(
#{name},
#{descr},
#{url},
now(),
now()
)
</insert>
- 3、在接口映射器中设置useGeneratedKeys参数
示例:
// 设置useGeneratedKeys为true,返回数据库自动生成的记录主键id
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("insert into test(name,descr,url,create_time,update_time) values(#{name},#{descr},#{url},now(),now())")
Integer insertOneTest(Test test);
该类设置方式优先级高于第一种方式。
3.2.2 使用< selectKey >子标签
示例:
<insert id="insert" parameterType="com.test.po.Student">
INSERT INTO student (name,score,age,gender)
VALUES (#{name},#{score},#{age},#{gender});
<selectKey keyProperty="id" order="BEFORE" resultType="int" >
SELECT LAST_INSERT_ID();
</selectKey>
</insert>
如果使用的是Mysql这样的支持自增主键的数据库,可以简单的使用第一种方式。对于不支持自增主键的数据库,如Oracle,则没有主键返回这一概念,而需要在插入之前先生成一个主键。此时可以用<selectKey>
标签,设置其order属性为BEFORE,并在标签体内写上生成主键的SQL语句,这样在插入之前,会先处理<selectKey>
,生成主键,再执行真正的插入操作。
使用
<selectKey>
标签来获取主键的方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
selectKey 元素描述:
<selectKey>
标签其实就是一条SQL,这条SQL的执行,可以放在主SQL执行之前或之后,并且会将其执行得到的结果封装到入参的Java对象的指定属性上。注意<selectKey>
子标签只能用在<insert>
和<update>
标签中。上面的LAST_INSERT_ID()实际上是MySQL提供的一个函数,可以用来获取最近插入或更新的记录的主键id。
获取主键示例:
public class MapperProxyTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student(-1, "Podman", 130, 15, 0);
mapper.insert(student);
sqlSession.commit();
System.out.println(student.getId());
}
}
四、动态SQL
4.1 Mybatis动态sql是做什么的
- 1、Mybatis的动态sql可以在xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。
- 2、Mybatis提供了9种动态sql标签:
trim|where|set|foreach|if|choose|when|otherwise|bind
- 3、其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
4.2 动态sql
即根据具体的参数条件,来对SQL语句进行动态拼接。Mybatis里的主要动态标签如下:
4.2.1 if*
当满足test条件时,才会将<if>
标签内的SQL语句拼接上去。示例:
<select id="find" resultType="student" parameterType="student">
SELECT * FROM student WHERE age >= 18
<if test="name != null and name != ''">
AND name like '%${name}%'
</if>
</select>
当有多个条件判断时,需要和where标签合起来使用。示例:
<select id="selectEmpByCondition" resultType="com.test.bean.Emp">
select * from emp
<where>
<if test="empno!=null">
empno > #{empno}
</if>
<if test="ename!=null">
and ename = #{ename}
</if>
</where>
</select>
4.2.2 choose*
示例:
<!-- choose 和 when , otherwise 是配套标签,类似于java中的switch,只会选中满足条件的一个-->
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
4.2.3 where*
<where>
标签只会在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加WHERE,并且如果WHERE之后是以AND或OR开头,会自动将其删掉。示例:
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
4.2.4 trim
<where>
标签可以用<trim>
标签代替。
trim标签一般用于去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作。示例:
<!--
trim截取字符串:
prefix:前缀,为sql整体添加一个前缀
prefixOverrides:去除整体字符串前面多余的字符
suffixOverrides:去除后面多余的字符串
-->
<select id="getEmpByCondition" resultType="com.test.bean.Emp">
select * from emp
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<if test="empno!=null">
empno > #{empno} and
</if>
<if test="ename!=null">
ename like #{ename} and
</if>
<if test="sal!=null">
sal > #{sal} and
</if>
</trim>
</select>
4.2.5 foreach*
用来做迭代拼接的,通常会与SQL语句中的IN查询条件结合使用,注意,到parameterType为List(链表)或者Array(数组),后面在引用时,参数名必须为list或者array。如在foreach标签中,collection属性则为需要迭代的集合,由于入参是个List,所以参数名必须为list。示例:
<select id="batchFind" resultType="student" parameterType="list">
SELECT * FROM student WHERE id in
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
一个较完整的例子。接口中的定义:
public List<Emp> selectEmpByDeptnos(@Param("deptnos") List<Integer> deptnos);
EmpDao.xml中的示例:
<!--
collection:指定要遍历的集合
separator:分隔符
open:以什么开始
close:以什么结束
item:遍历过程中的每一个元素值
index:表示索引
-->
<select id="selectEmpByDeptnos" resultType="com.test.bean.Emp">
select * from emp where deptno in
<foreach collection="deptnos" separator=","
open="(" item="deptno" index="idx" close=")">
#{deptno}
</foreach>
</select>
测试代码:
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List<Emp> list = mapper.selectEmpByDeptnos(Arrays.asList(10, 20));
4.2.6 sql
可将重复的SQL片段提取出来,然后在需要的地方,使用<include>
标签进行引用。示例:
<select id="findUser" parameterType="user" resultType="user">
SELECT * FROM user
<include refid="whereClause"/>
</select>
<sql id="whereClause">
<where>
<if test user != null>
AND username like '%${user.name}%'
</if>
</where>
</sql>
4.3 Mybatis如何执行批量操作
4.3.1 使用foreach标签*
使用该方式时,其中一种方式是在db链接url后面带一个参数:&allowMultiQueries=true
,示例:
jdbc.url=jdbc:mysql://localhost:3306/common_mapper?useUnicode=true
&characterEncoding=utf8&allowMultiQueries=true
示例:
<update id="updateQuestionseleteTempalteList" parameterType="java.util.List">
<foreach collection="list" item="item" index="index">
update tb_question_template_seleteitem_detail
set selectedName = #{item.selectedName}
where 1=1 and selectedId = #{item.selectedId}
</foreach>
</update>
foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item,index,collection,open,separator,close。
- item 表示集合中每一个元素进行迭代时的别名,随便起的变量名;
- index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
- open 表示该语句以什么开始,常用“(”;
- separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
- close 表示以什么结束,常用“)”。
以下为不使用&allowMultiQueries=true
的示例。
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
1、如果传入的是单参数且参数类型是一个List
,collection属性值为list,示例:
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
2、如果传入的是单参数且参数类型是一个array数组
,collection的属性值为array,示例:
<select id="dynamicForeach2Test" resultType="Blog">
select * from t_blog where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
3、如果传入的参数是多个
,就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key。示例:
<!-- 批量保存(foreach插入多条数据两种方法)
int addEmpsBatch(@Param("emps") List<Employee> emps); -->
<!-- MySQL下批量保存,可以foreach遍历 mysql支持values(),(),()语法,推荐使用 -->
<insert id="addEmpsBatch">
INSERT INTO emp(ename,gender,email,did)
VALUES
<foreach collection="emps" item="emp" separator=",">
(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
4.3.2 使用ExecutorType.BATCH
Mybatis内置的ExecutorType有3种,默认为simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优; 但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,这在某型情形下是不符合业务要求的。具体用法如下:
//批量保存方法测试
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 1000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}
openSession.commit();
} finally {
openSession.close();
}
}
mapper和mapper.xml如下:
public interface EmployeeMapper {
//批量保存员工
Long addEmp(Employee employee);
}
<mapper namespace="com.jourwon.mapper.EmployeeMapper"
<!--批量保存员工 -->
<insert id="addEmp">
insert into employee(lastName,email,gender)
values(
#{lastName},
#{email},
#{gender}
)
</insert>
</mapper>
五、其它相关问题
5.1 #{}和${}*
5.1.1 两者的使用场景
- 1、用#{}获取实体类属性
如果入参类型是实体类:
public class Student{
private String name;
private Integer age;
//...
}
#{name}
表示取入参对象Student中的name属性,#{age}
表示取age属性,这个过程是通过反射实现的。
- 2、用${}做模糊查询
${}的作用是字符串替换,一般会用在模糊查询的情景,比如:
SELECT * FROM student WHERE name like '%${name}%'
它的处理阶段在#{}之前,不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为:
SELECT * FROM student WHERE name like '%zhangsan%'
如果此时用的是SELECT * FROM student WHERE name like '%#{name}%'
,这条SQL最终就会变成SELECT * FROM student WHERE name like '%'zhangsan'%'
,所以模糊查询只能用${}。
- 3、sql注入风险
普通的入参(如String类型参数)也可以用${}
,但不能用${}
。因为${}
不会做类型解析,存在SQL注入的风险。示例:
SELECT * FROM user WHERE name = '${name}' AND password = '${password}'
假如password属性为'OR '1' = '1
,最终的SQL会变成:
SELECT * FROM user WHERE name = 'test' AND password = ''OR '1' = '1'
因为OR '1' = '1'
恒成立,这个where条件就不起作用了。
5.1.2 两者的区别*
- 两者的区别
#{}是占位符,预编译处理
;${}是拼接符,字符串替换
,没有预编译处理。
Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为"?",调用PreparedStatement的set方法来赋值。
Mybatis在处理 $ {}时,就是把${}替换成变量的值。- #{}方式能够很大程度防止sql注入,原因在于预编译机制;${}方式无法防止Sql注入。
一般能用#{}的就别用${}
。
- ${}的使用场景
1)$方式一般用于传入数据库对象
。
当需要传入动态的表名、列名的时候就需要使用${}
。因为使用#{}拼接表名的话,会有多余的字符串。示例:
select * from #{param}
//传入值为user时,就会拼接处下面的sql
select * from 'user'
2)用于排序字段
按某个字段排序,升降序也需要使用${}
来指定。示例:
//接口
List<Userinfo> sortAll(@Param("sort") String userinfo);
//测试
@Test
void sortAll() {
String sort = "desc";
List<Userinfo> userinfos = userMapper.sortAll(sort);
System.out.println(userinfos);
}
如果使用#{},如:
<select id="sortAll" resultType="com.example.demo.entity.Userinfo">
select * from userinfo order by id #{sort}
</select>
那么拼接出来的sql是 select * from userinfo order by id 'desc';
,显然有问题,所以要用${}。
3)用于模糊查询
因为有注入风险,所以一般用concat函数代替。
5.2 模糊查询like语句该怎么写*
模糊查询的写法不止一种。
- 1)’%${question}%’ 可能引起SQL注入,不推荐。
- 2)
CONCAT('%',#{question},'%') 使用CONCAT()函数,推荐
。 - 3)使用bind标签(不常用)
<select id="listUserLikeUsername" resultType="com.test.pojo.User">
<bind name="pattern" value="'%' + username + '%'" />
select id,sex,age,username,password
from person
where username LIKE #{pattern}
</select>
5.3 Mybatis常用注解*
- @TableName/@TableId/@TableField
@TableName用来将指定的数据库表和JavaBean进行映射。
@TableId用于将某个成员变量指定为数据表主键。
@TableField用于标识非主键的字段。将数据库列与 JavaBean 中的属性进行映射。示例:
@TableName(value = "user")
public class UserBean {
@TableId(value = "user_id", type = IdType.AUTO)
private String userId;
@TableField("name")
private String name;
@TableField("sex")
private String sex;
@TableField("age")
private Integer age;
}
- @Param
@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中。示例:
public int getUsersDetail(@Param("userid") int userid);
- @MapperScan/@Mapper
指定要变成实现类的接口所在的包,包下面的所有接口在编译之后都会生成相应的实现类。
在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类。
示例:
@SpringBootApplication
@MapperScan({"com.kfit.demo","com.kfit.user"})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
@Mapper
public interface Inter {
@select("select * from sysuser where userid=#{id}")
int queryUserByid(int id);
}
5.4 Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
还有很多其他的标签,<resultMap>、<parameterMap>、<sql>、<include>、 <selectKey>
,加上动态sql的9个标签trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>
为sql片段标签,通过<include>
标签引入sql片段,<selectKey>
为不支持自增的主键生成策略标签。
5.5 Mybatis映射文件中,如果A标签通过include引用了B标签的内容,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别
。
原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。
5.6 关联查询*
主要使用<resultMap>
标签以及<association>
和<collection>
子标签,进行关联查询。
- 1、一对多查询
以一个Category对应多个Product为例,一对多查询时,用到的标签为collection
,写法示例:
<mapper namespace="com.test.pojo">
<resultMap id="categoryBean" type="Category" >
<id column="cid" property="id" />
<result column="cname" property="name" />
<!-- 一对多的关系 -->
<!-- property: 指的是集合属性的值, ofType:指的是集合中元素的类型 -->
<collection property="products" ofType="Product">
<id column="pid" property="id" />
<result column="pname" property="name" />
<result column="price" property="price" />
</collection>
</resultMap>
<!-- 关联查询分类和产品表 -->
<select id="listCategory" resultMap="categoryBean">
select
c.*,
p.*,
c.id 'cid',
p.id 'pid',
c.name 'cname',
p.name 'pname'
from category_ c
left join product_ p on c.id = p.cid
</select>
</mapper>
- 2、一对一查询
一对一查询时,用到的标签为:association
,写法示例:
<mapper namespace="com.test.pojo">
<resultMap id="productBean" type="Product" >
<id column="pid" property="id" />
<result column="pname" property="name" />
<result column="price" property="price" />
<!-- 一对一的关系 -->
<!-- property: 指的是属性名称, javaType:指的是属性的类型 -->
<association property="category" javaType="Category">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</association>
</resultMap>
<!-- 根据id查询Product, 关联将Orders查询出来 -->
<select id="listProduct" resultMap="productBean">
select
c.*,
p.*,
c.id 'cid',
p.id 'pid',
c.name 'cname',
p.name 'pname'
from category_ c
left join product_ p on c.id = p.cid
</select>
</mapper>
5.7 Mybatis是否可以映射Enum枚举类
Mybatis可以映射枚举类。不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。
5.7.1 枚举类型转换器
对于枚举类型变量,Mybatis有两种类型转换器:EnumTypeHandler和EnumOrdinalTypeHandler。
- EnumTypeHandler
默认的枚举转换器,该转换器将枚举实例转换为实例名称的字符串,如将 SexEnum.MAN转换MAN。 - EnumOrdinalTypeHandler
将枚举实例的ordinal属性(int值,表示的含义是存入枚举类中的值的次序,从0开始)作为取值。如SexEnum.MAN的ordinal属性是0,SexEnum.WOMAN的ordinal属性是1。
5.7.2 自定义类型转换器*
如果系统的类型转换器不够用的话,可以自定义类型转换器。
比如Java代码里有这个这样的字符串:“dwad21.jpg,dwad22.jpg,dwad.23.jpg”,如果我在sql表中有一个字段(local_url)是本地图片资源的多个url字符串拼接值。我想在java后端中不进行额外的转换就取值加值。我需要的是里面的dwad21.jpg,最好做集合处理。
- 使用sql映射到对象的时候就把把字符串变成集合,即 :dwad21.jpg,dwad22.jpg,dwad.23.jpg -> {dwad21.jpg,dwad22.jpg,dwad.23.jpg}
- 使用insert或者update的时候自动把集合变成字符串,即:{dwad21.jpg,dwad22.jpg,dwad.23.jpg} =>dwad21.jpg,dwad22.jpg,dwad.23.jpg
要实现自定义类型处理器,有两种方式:
- 实现org.apache.ibatis.type.TypeHandler接口。
- 继承org.apache.ibatis.type.BaseTypeHandler类。
以实现接口的方式实现自定义类型转换器为例。具体做法为:实现TypeHandler的setParameter()和getResult()接口方法。TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。
- 1、实现TypeHandler接口
public class StringToListTypeHandler implements TypeHandler<List<String>> {
@Override
public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
if (parameter != null) {
ps.setString(i, String.join(",", parameter));
} else {
ps.setNull(i, jdbcType.TYPE_CODE);
}
}
@Override
public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
String columnValue = rs.getString(columnName);
return convertStringToList(columnValue);
}
@Override
public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValue = rs.getString(columnIndex);
return convertStringToList(columnValue);
}
@Override
public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
String columnValue = cs.getString(columnIndex);
return convertStringToList(columnValue);
}
private List<String> convertStringToList(String columnValue) {
if (columnValue != null) {
return Arrays.asList(columnValue.split(","));
} else {
return null;
}
}
}
setParameter :将Java对象的参数设置到PreparedStatement中,通常用于将参数绑定到SQL语句中的占位符。在这个示例中,它将List类型的参数转换为逗号分隔的字符串,并设置到PreparedStatement中。如果参数为null,它将设置为数据库类型的null值。
getResult(ResultSet rs, String columnName):从ResultSet对象中获取结果,根据列名columnName获取对应的列的值。
getResult(ResultSet rs, int columnIndex):从ResultSet对象中获取结果,根据列索引columnIndex获取对应的列的值。
getResult(CallableStatement cs, int columnIndex):从CallableStatement对象中获取结果,根据列索引columnIndex获取对应的列的值。通常,这种情况用于从存储过程中获取结果。
- 2、注册类型转换器
示例:
<typeHandlers>
<typeHandler handler="com.chen.behindimagesmanage.handler.StringToListTypeHandler" javaType="java.util.List<java.lang.String>" jdbcType="VARCHAR"/>
</typeHandlers>
- 3、在xml文件中使用自定义的类型转换区
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chen.behindimagesmanage.dao.FileDao">
<!-- 定义一个查询语句 -->
<resultMap id="imageMetaDataResultMap" type="com.chen.behindimagesmanage.pojo.ImageMetaData">
<id property="id" column="id" />
<result property="md5" column="md5" />
<result property="aliyunUrl" column="aliyun_url" />
<result property="localUrl" column="local_url" typeHandler="com.chen.behindimagesmanage.handler.StringToListTypeHandler" />
<result property="version" column="version" />
</resultMap>
<select id="getAllImg" resultType="com.chen.behindimagesmanage.pojo.ImageMetaData">
SELECT * FROM image_metadata
</select>
<!-- 定义一个更新语句 -->
<parameterMap id="imageMetaDataParamMap" type="com.chen.behindimagesmanage.pojo.ImageMetaData">
<parameter property="id" jdbcType="INTEGER" javaType="java.lang.Integer" mode="IN"/>
<parameter property="md5" jdbcType="VARCHAR" javaType="java.lang.String" mode="IN"/>
<parameter property="aliyunUrl" jdbcType="VARCHAR" javaType="java.lang.String" mode="IN"/>
<parameter property="localUrl" typeHandler="com.chen.behindimagesmanage.handler.StringToListTypeHandler"/>
<parameter property="version" jdbcType="INTEGER" javaType="int" mode="IN"/>
</parameterMap>
<update id="updateLocalUrl"
>
UPDATE image_metadata SET local_url= #{localUrl,typeHandler = com.chen.behindimagesmanage.handler.StringToListTypeHandler}, version = #{localUrl} + 1 WHERE version = #{vsersion}
</update>
</mapper>
5.8 Mybatis是如何进行分页的?分页插件的原理是什么?
当查询的数据量过大时,一把要用到分页查询。分页的实现方法有:limit分页、RowBounds分页和PageHelper分页插件。
5.8.1 limit分页
这种方式指开发者自己写limit子句,示例:
<if test="startPos!=null and pageSize!=null">
limit ${startPos},${pageSize}
</if>
5.8.2 RowBounds分页
RowBounds帮我们省略了limit的内容(即不用开发者自己写limit子句),我们只需要在业务层关注分页。但是,这个属于逻辑分页,即实际上sql查询的是所有的数据,在业务层进行了分页而已,比较占用内存,而且数据更新不及时,可能会有一定的滞后性,不推荐使用。
RowBounds对象有2个属性,offset和limit。 offset:起始行数 limit:需要的数据行数 因此,取出来的数据就是:从第offset+1行开始,取limit行。示例:
@Test
public void selectUserRowBounds() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.getUserInfoRowBounds(new RowBounds(0,5));
for (User map: users){
System.out.println(map);
}
session.close();
}
<select id="getUserInfoRowBounds" resultType="dayu">
select * from user
</select>
5.8.3 PageHelper分页插件*
这是最常用的分页方式,使用步骤如下。
- 1、引入jar包
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.7</version>
</dependency>
- 2、配置分页插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
- 3、业务层代码
@Test
public void selectUserPageHelper() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
PageHelper.startPage(1, 3);
List<User> list = mapper.getUserInfo();
//用PageInfo将包装起来
PageInfo page = new PageInfo(list);
for (User map: list){
System.out.println(map);
}
System.out.println("page:---"+page);
session.close();
}
PageHelper比较好用,是物理分页。
在使用PageHelper插件时,两个分页参数的传递有很多种。比如:
//1.在业务代码中调用PageHelper.startPage方法.
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);
//2.在Mapper接口中传pageNum、pageSize参数,开发者不需要在xml处理后两个参数
List<User> selectByPageNumSize(
@Param("user") User user,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
//3.将两个分页参数存在实体类对象中.如pageNum和pageSize存在于User对象中,只要参数
//有值,也会被分页
public class User {
//...
//下面两个参数名和 params 配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
//存在以下 Mapper 接口方法,开发者不需要在 xml 处理后两个参数
public interface CountryMapper {
List<User> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<User> list = userMapper.selectByPageNumSize(user);
}
使用PageHelper分页插件时,需注意,只对startPage后最近的一个查询sql有分页效果。原因是PageHelper用拦截器的方式给,查询sql添加limit子句,进而达到分页效果,在这个过程中,用到了ThreadLocal。在执行最近的一次查询后,在finally内把ThreadLocal中的分页数据给清除掉了,所以只要执行一次查询语句就会清除分页信息,故而后面的select语句自然就无效了。
5.9 Mybatis和MybatisPlus的比较*
MybatisPlus是一个Mybatis的增强工具,在MyBatis的基础上只做增强不做改
变,简化开发、提高效率。
5.9.1 MybatisPlus的特性
MybatisPlus官网上给出了MybatisPlus的12个特性。
- 1、无侵入
由于MybatisPlus是在Mybatis基础上做了功能扩展,只做增强不做改变,引入它不会对现有工程产生影响。 - 2、损耗小
启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作。 - 3、强大的CRUD操作
内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作,更有强大的条件构造器,满足各类使用需求。 - 4、支持Lambda形式调用
通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错。 - 5、支持主键自动生成
支持多达4种主键策略(内含分布式唯一ID生成器:Sequence),可自由配置,完美解决主键问题。 - 6、支持ActiveRecord模式
支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作。 - 7、支持自定义全局通用操作
支持全局通用方法注入( Write once, use anywhere )。 - 8、内置代码生成器
采用代码或者Maven插件可快速生成 Mapper 、Model 、Service 、 Controller层代码,支持模板引擎,更有超多自定义配置等您来使用。 - 9、内置分页插件
基于MyBatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询。 - 10、分页插件支持多种数据库
支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer等多种数据库。 - 11、内置性能分析插件
可输出SQL语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询。 - 12、内置全局拦截插件
提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作。
5.9.2 Mybatis和MybatisPlus使用的差别*
以下列举几点两者在使用上的差别。
- 1、驼峰映射
在Mybatis中,驼峰映射默认是关闭的;在MybatisPlus中是默认打开的。如设置不使用驼峰规则:
mybatis-plus.configuration.map-underscore-to-camel-case=false
- 2、实体类和表对应关系的一些注解
在MybatisPlus中,有一些注解,可以用来表示实体类和数据库表的对应关系。常见的有:@TableName、@TableId、@TableField。
@TableName("table_name")
:在数据库表对应的实体类中,指定该实体类在数据库中对应的表名。示例:
@TableName("test_user")
@Data
public class User implements Serializable {
}
@TableId(ype=IdType.AUTO)
:若type=IdType.AUTO则表示id自增长,一般用来注解在主键对应的字段上。示例:
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField(value="email")
:指定某个实体类字段在数据库中的字段名。如果实体类字段名和字段名符合下划线和驼峰的转换关系的话,该注解可以不使用。但通常为了代码的易读性,使用该注解,示例:
@TableField("tag_code")
private String tagCode;
@TableField(exist=false)
:通常我们在写查询sql时,会将查询结果封装成一个实体类,实体类属性为数据库表对应的字段。如果某个查询字段不是数据库中的字段,就需要在该字段上使用该注解,表示该字段不对应数据中的任何字段。
- 3、是否可以不写SQL
这应该是Mybatis和MybatisPlus最核心的区别之一了。Mybatis是必须要写sql的,无论是用注解还是xml文件的方式。而MybatisPlus则不用写sql就可以实现基本的CRUD功能。
Mybatis需要写sql,常用方式有2种:注解和xml的形式。
Mybatis1:注解的方式
用注解的方式写sql,也就是将sql写在Java代码中。示例:
@Select("select \* from Type where id = #{id, jdbcType=LONG} and code= #{code, jdbcType=VARCHAR}")
Type selectTypeById(@Param("id") Long id, @Param("code") String code);
这种写法,不仅耦合性高,而且可读性差,不易维护,所以这种方式不推荐使用。
Mybatis2:xml的方式
将sql写在XxxMapper.java对应的XxxMapper.xml文件中,这是使用Mybatis时常用的做法。示例:
<select id="queryUserByCustNo" parameterType="java.lang.String" resultType="com.test.entity.User">
select
cust_no as custNo,
age as age,
name as name
from
test_user
where
cust_no = #{custNo}
</select>
这样的作法无可厚非,但不方便的是,即便是再简单的sql,也需要写对应的xml文件,增加一些工作量。
对于基本的CRUD,MybatisPlus则不用写对应xml文件,就可以实现。具体的步骤如下:
1、指定了具体数据库表的实体类
示例:
@TableName("test_user")
@Data
public class User implements Serializable {
}
2、继承了BaseMapper的类
编写一个XxxMapper文件,继承BaseMapper,这样不写具体的XxxMapper.xml文件,就可以使用简单的CRUD功能。示例:
// 继承BaseMapper,User是数据库表对应的实体类
public interface UserMapper extends BaseMapper<User> {
}
3、使用继承了BaseMapper的类
在查询时,经常会用到QueryWrapper,查询结果有2种:1条记录或多条记录,分别对应selectOne方法和selectList方法。示例:
// 查询1条记录
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("cust_no", user.getCustNo());
List<User> userList = userMapper.selectOne(wrapper);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", "1");
List<User> userList = userMapper.selectList(wrapper);
在实现基本的查询时,除了使用QueryWrapper,也可以使用EntityWrapper,两者在简单的查询上没啥差别。示例:
EntityWrapper<User> queryWrapper = new EntityWrapper<User>();
queryWrapper.eq("age", 23);
List<User> loanInfoEntities = userMapper.selectList(queryWrapper);
使用MybatisPlus实现简单的增删改
其实不仅查询功能,实现了BaseMapper的类,还可以在不写sql的情况下,实现修改、新增、删除功能。示例:
// 新增
User user = new User();
user.setName("zhangsan");
user.setAge(23);
userMapper.insert(user);
// 根据id进行删除
User user = new User();
user.setId("123456789");
sresult = userMapper.deleteById(user);
//根据name更新,其他属性不变
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name","zhangsan");
User user = new User();
user.setAge(18);
userMapper.update(user, updateWrapper);
MybatisPlus的Lambda形式调用
除了实现简单的CRUD,MybatisPlus也可以用Lambda的形式实现复杂的条件查询,实现的方式较多,如:EntityWrapper、QueryWrapper等。
- 1、EntityWrapper
EntityWrapper userWrapper = new EntityWrapper<>();
userWrapper.like("name", "Tom").eq("age", 18)
.eq("email", "test@mp.com").orderBy("age").orderBy("id", false);
List userList = userService.selectList(userWrapper);
- 2、QueryWrapper
List<User> userList = userMapper.selectList(new QueryWrapper<User>()
.like("email", "24252")
.between("age", 20, 22)
.or()
.eq("name", "zcx")
);
以上的用Lambda形式动态拼接sql的方式,偏向于面向对象的方式。对于复杂的sql,还是建议在xml文件中写,耦合性好,易读易维护。
5.9.3 使用MybatisPlus时的注意事项*
以上是MybatisPlus查询的最基本使用。在查询时,需要注意的是,在实体类中,一定要使用包装类,而不能使用基本类型。以下是错误的示例:
@TableName("test_user")
@Data
public class User implements Serializable {
@TableField("status")
private String status;
@TableField("age")
private int age;
//....
}
以下为查询代码:
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", "1");
List<User> userList = userMapper.selectList(wrapper);
上面的查询sql可能会差不出来数据,因为age字段是基本数字类型int,默认值为0,所以上面的代码在转换为sql时,转换为的sql是:
select * from test_user where status = '1' and age = 0
所以在写实体类时,尽量使用对象类型,不使用基本类型,会避免掉一些不必要的问题。
5.10 Mybatis和JPA的比较*
spring data jpa默认使用hibernate作为ORM实现,是spring 提供的一套jpa接口,使用spring data jpa主要完成一些简单的增删改查功能。
对于复杂的查询功能会使用mybatis编写sql语言来实现,因为使用spring data jpa来做一些复杂的查询没有mybatis方便,spring data jpa是面向对象,而mybatis直接面向sql语句。
- 1、ORM映射不同
Mybatis是半自动的ORM框架,提供数据库与结果集的映射;
JPA(Hibernate)是全自动的ORM框架,提供对象与数据库的映射; - 2、可移植性不同
JPA(Hibernate)通过它强大的映射结构和hql语言,大大降低了对象与数据库(oracle、mysql等)的耦合性。
Mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。 - 3、日志系统的完整性不同
JPA(Hibernate)日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而Mybatis则除了基本记录功能外,功能薄弱很多。 - 4、SQL优化上的区别
由于Mybatis的sql都是写在xml里,因此优化sql比Hibernate方便很多。而Hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也歇菜,也就是说hql是有局限的;hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上Hibernate不及Mybatis。
【JPA使用的例子】
实体类:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
/**
* - @GeneratedValue :这是一个JPA注解,用于指定主键的生成方式。
* - strategy = GenerationType.IDENTITY :是@GeneratedValue注解的一个属性,
* 表示使用数据库的自增长方式来生成主键。
*/
private Long id;
private String name;
private int age;
// 省略构造函数、getter和setter方法
}
Repository接口:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Service层:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
//查询所有
public List<User> getAllUsers() {
return userRepository.findAll();
}
//通过id查询
public User getUserById(Long id) {
Optional<User> optionalUser = userRepository.findById(id);
return optionalUser.orElse(null);
}
//插入和更新
public User saveUser(User user) {
return userRepository.save(user);
}
//根据id删除
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
//分页查询
public Page<User> getUsersByPage(int pageNumber, int pageSize) {
Pageable pageable = PageRequest.of(pageNumber, pageSize);
return userRepository.findAll(pageable);
}
}
Controller层:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping
public User saveUser(@RequestBody User user) {
return userService.saveUser(user);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
User existingUser = userService.getUserById(id);
if (existingUser != null) {
existingUser.setName(user.getName());
existingUser.setAge(user.getAge());
return userService.saveUser(existingUser);
}
return null;
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
@GetMapping("/page")
public Page<User> getUsersByPage(@RequestParam int pageNumber, @RequestParam int pageSize) {
return userService.getUsersByPage(pageNumber, pageSize);
}
}