Mybatis(二)Mybatis的高级使用*

文章目录

本系列文章:
  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绑定起来:

  1. mapper接口的全限定名,要和mapper.xml的namespace属性一致;
  2. mapper接口中的方法名要和mapper.xml中的SQL标签的id一致;
  3. mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致;
  4. 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 代理主键和自然主键

  1. 自然主键是指事物属性中的自然唯一标示(例如身份证号);
  2. 代理主键是指与业务无关的,无意义的数字序列值;
  3. 在表设计时,优先推荐代理主键,不推荐自然主键(代理主键无意义,所以与业务解耦。另一方面,自然主键是自然界的事物,一般为字符串,处理麻烦)。

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 两者的区别*
  • 两者的区别
  1. #{}是占位符,预编译处理${}是拼接符,字符串替换,没有预编译处理。
      Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为"?",调用PreparedStatement的set方法来赋值。
      Mybatis在处理 $ {}时,就是把${}替换成变量的值。
  2. #{}方式能够很大程度防止sql注入,原因在于预编译机制;${}方式无法防止Sql注入。
  3. 一般能用#{}的就别用${}
  • ${}的使用场景

 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,最好做集合处理。

  1. 使用sql映射到对象的时候就把把字符串变成集合,即 :dwad21.jpg,dwad22.jpg,dwad.23.jpg -> {dwad21.jpg,dwad22.jpg,dwad.23.jpg}
  2. 使用insert或者update的时候自动把集合变成字符串,即:{dwad21.jpg,dwad22.jpg,dwad.23.jpg} =>dwad21.jpg,dwad22.jpg,dwad.23.jpg

  要实现自定义类型处理器,有两种方式:

  1. 实现org.apache.ibatis.type.TypeHandler接口。
  2. 继承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);
    }
}
  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MyBatis-Plus 是一个基于 MyBatis 的增强工具,提供了许多便捷的功能和高级用法,以提升开发效率。以下是一些 MyBatis-Plus 的高级用法: 1. 代码生成器:MyBatis-Plus 提供了一个代码生成器,能够根据数据库表结构自动生成对应的实体类、Mapper 接口以及 XML 映射文件,极大地减少了手动编写重复代码的工作量。 2. 自动填充:通过使用 @TableField 注解中的 fill 属性,可以实现在插入或更新数据时自动填充某些字段的值,例如创建时间、更新时间等。 3. 条件构造器:MyBatis-Plus 提供了强大的条件构造器,可通过链式调用的方式灵活地构建查询条件。例如,使用 wrapper.eq("name", "张三") 可以构造出 name = '张三' 的查询条件。 4. 分页查询:通过使用 Page 类,可以实现简单的分页查询。可以通过 PageHelper.startPage 方法设置页码和每页显示数量,并将 Page 对象传入查询方法中,查询结果将自动填充到 Page 对象中。 5. 逻辑删除:通过在实体类的字段上标注 @TableLogic 注解,可以实现逻辑删除的功能。被标记为逻辑删除的字段在删除操作时不会直接删除记录,而是更新该字段的值表示删除状态。 6. 动态 SQL:MyBatis-Plus 提供了强大的动态 SQL 语法支持,可以根据不同条件动态生成不同的 SQL 语句,提供了 if、choose、when、otherwise 等标签,使得 SQL 编写更加灵活。 以上是 MyBatis-Plus 的一些高级用法,它们都能够帮助开发人员简化开发流程并提高效率。更多详细的用法可以参考 MyBatis-Plus 官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值