Mybatis- 应用

主键返回

通常我们会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢?有两种方式

  1. 使用useGeneratedKeys和keyProperty属性

    <insert id="insert" parameterType="com.yogurt.po.Student" useGeneratedKeys="true" keyProperty="id">
         INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
    </insert>
    
  2. 使用<selectKey>子标签

    <insert id="insert" parameterType="com.yogurt.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,则没有主键返回这一概念,而需要在插入之前先生成一个主键。此时可以用标签,设置其order属性为BEFORE,并在标签体内写上生成主键的SQL语句,这样在插入之前,会先处理,生成主键,再执行真正的插入操作。
<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标签的使用,注意如果parameterType是List的话,则在标签体内引用这个List,只能用变量名list,如果parameterType是数组,则只能用变量名array

<select id="batchFind" resultType="student" parameterType="java.util.List">
        SELECT * FROM student
        <where>
            <if test="list != null and list.size() > 0">
                AND id in
                <foreach collection="list" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
        </where>
</select>

动态SQL

可以根据具体的参数条件,来对SQL语句进行动态拼接。
比如在以前的开发中,由于不确定查询参数是否存在,许多人会使用类似于where 1 = 1 来作为前缀,然后后面用AND 拼接要查询的参数,这样,就算要查询的参数为空,也能够正确执行查询,如果不加1 = 1,则如果查询参数为空,SQL语句就会变成SELECT * FROM student where,SQL不合法。

mybatis里的动态标签主要有

  1. if

    <!-- 示例 -->
    <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>
    

    当满足test条件时,才会将标签内的SQL语句拼接上去

  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>
    
  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. trim
    <where>标签可以用<trim>标签代替

    <trim prefix="WHERE" prefixOverrides="AND | OR">
       ...
    </trim>
    
  5. set
    在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加SET,并且如果SET之后是以,开头的话,会自动将其删掉<set>标签相当于如下的<trim>标签

    <trim prefix="SET" prefixOverrides=",">
       ...
    </trim>
    
    
  6. 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>
    
    
  7. sql

    <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>
    
    

    可将重复的SQL片段提取出来,然后在需要的地方,使用<include>标签进行引用

  8. bind
    mybatis的动态SQL都是用OGNL表达式进行解析的,如果需要创建OGNL表达式以外的变量,可以用bind标签

    <select id="selectBlogsLike" resultType="Blog">
      <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
      SELECT * FROM BLOG
      WHERE title LIKE #{pattern}
    </select>
    
    
缓存
  • 一级缓存
    默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库

    一级缓存在下面情况会被清除

    • 在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存
    • SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
    • 对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在mybatis中会被封装成一个MappedStatement)
    • 在全局配置文件中设置 <setting name="localCacheScope" value="STATEMENT"/>,这样会使一级缓存失效,二级缓存不受影响
  • 二级缓存
    默认关闭,可通过全局配置文件中的开启二级缓存总开关,然后在某个具体的mapper.xml中增加,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中

延迟加载

延迟加载是结合关联查询进行应用的。也就是说,只在<association><collection> 标签上起作用

对于关联查询,若不采用延迟加载策略,而是一次性将关联的从信息都查询出来,则在主信息比较多的情况下,会产生N+1问题,导致性能降低。比如用户信息和订单信息是一对多的关系,在查询用户信息时,设置了关联查询订单信息,如不采用延迟加载策略,假设共有100个用户,则我们查这100个用户的基本信息只需要一次SQL查询

若开启了关联查询,且不是延迟加载,则对于这100个用户,会发出100条SQL去查用户对应的订单信息,这样会造成不必要的性能开销(其实我认为称之为1+N问题更为合适)

当我们可能只关心id=3的用户的订单信息,则很多的关联信息是无用的,于是,采用延迟加载策略,可以按需加载从信息,在需要某个主信息对应的从信息时,再发送SQL去执行查询,而不是一次性全部查出来,这样能很好的提升性能。

另外,针对N+1问题,除了采用延迟加载的策略按需进行关联查询。如果在某些场景下,确实需要查询所有主信息关联的从信息。在上面的例子中,就是如果确实需要把这100个用户关联的订单信息全部查询出来,那怎么办呢?这里提供2个解决思路。

  • 采用连接查询,只使用1条SQL即可,如下
    select * from user as u left join orders as o on u.id = o.u_id;
    但使用连接查询查出来的结果是两表的笛卡尔积,还需要自行进行数据的分组处理
  • 使用两个步骤来完成,先执行一条SQL,查出全部的用户信息,并把用户的id放在一个集合中,然后第二条SQL采用IN关键字查询即可。这种方式也可以简化为子查询,如下
    select * from orders where u_id in (select id from user);

现在说回来,mybatis的延迟加载默认是关闭的,可以通过全局配置文件中的<setting name="lazyLoadingEnabled" value="true"/>来开启,开启后,所有的SELECT查询,若有关联对象,都会采用延迟加载的策略。当然,也可以对指定的某个CRUD标签单独禁用延迟加载策略,通过设置SELECT标签中的fetchType=eager,则可以关闭该标签的延迟加载。

(还有一个侵入式延迟加载的概念,在配置文件中通过<setting name="aggressiveLazyLoading" value="true">来开启,大概是说,访问主对象中的主信息时,就会触发延迟加载,将从信息查询上来,这其实并不是真正意义的延迟加载,真正意义上的延迟加载应该是访问主对象中的从信息时,才触发延迟加载,去加载从信息,侵入式延迟加载默认是关闭的,一般情况下可以不用管他)
注意,延迟加载在关联查询的场景下才有意义。需要配合<resultMap>标签下的<association><collecction> 标签使用

<!-- StudentMapper.xml -->
<resultMap id="studentExt" type="com.yogurt.po.StudentExt">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="score" column="score"/>
        <result property="age" column="age"/>
        <result property="gender" column="gender"/>
		<!-- 当延迟加载总开关开启时,resultMap下的association和collection标签中,若通过select属性指定嵌套查询的SQL,则其fetchType默认是lazy的,当在延迟加载总开关开启时,需要对个别的关联查询禁用延迟加载时,才有必要配置fetchType = eager -->
    	<!--
 		column用于指定用于关联查询的列
		property用于指定要封装到StudentExt中的哪个属性
		javaType用于指定关联查询得到的对象
		select用于指定关联查询时,调用的是哪一个DQL
		-->
        <association property="clazz" javaType="com.yogurt.po.Clazz" column="class_id"
                     select="com.yogurt.mapper.ClassMapper.findById" fetchType="lazy"/>

    </resultMap>

    <select id="findLazy" parameterType="string" resultMap="studentExt">
        SELECT * FROM student WHERE name like '%${value}%';
    </select>

<!-- com.yogurt.mapper.ClassMapper -->
<select id="findById" parameterType="int" resultType="com.yogurt.po.Clazz">
        SELECT * FROM class WHERE id = #{id}
</select>

PageHelper分页插件

使用该插件,快速实现查询结果的分页,使用步骤如下

  • pom.xml中配置依赖
    <dependency>
    	<groupId>com.github.pagehelper</groupId>
    	<artifactId>pagehelper</artifactId>
    	<version>5.1.6</version>
    </dependency>
    
    
  • mybatis全局配置文件中配置<plugin>标签
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <properties resource="properties/xx.properties"></properties>
    
        <plugins>
            <plugin interceptor="com.github.pagehelper.PageInterceptor">
                <property name="helperDialect" value="mysql"/>
            </plugin>
        </plugins>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${db.driver}"/>
                    <property name="url" value="${db.url}"/>
                    <property name="username" value="${db.user}"/>
                    <property name="password" value="${db.password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <package name="mybatis.generator.dao"/>
        </mappers>
    
    </configuration>
    
    
  • 在执行查询之前,先设置分页信息
    // 查询第一页,每页3条信息
    PageHelper.startPage(1,3);
    

PageHelper会先查询总数量,然后再发出分页查询,打开mybatis的日志时,可以看到发出了2条SQL
当开启PageHelper时,查询得到的List实际是PageHelper中自定义的一个类Page,这个类实现了List接口,并封装了分页的相关信息(总页数,当前页码等)。
可以通过PageInfo来获取分页的相关信息

Mybatis Plus

mybatis虽然非常方便,但也需要编写大量的SQL语句,于是mybatis plus就应运而生了。它是一个mybatis增强工具,为了简化开发,提高效率。搭配Spring-Boot食用简直不要太爽。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值