关于Mybatis的知识点总结

参考原文链接 如有侵权联系删

一.Mybatis常用标签(select,update,insert,delete)

1.select 运用

简单查询的 select 元素是非常简单的。比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键可以理解为数据库中结果集的列名,值是结果行中的对应值。(注意查询数据库中的值符号为 #{}

(1)select 常用标签元素
属性描述
id可以用id来调用此语句,是命名空间的唯一标识符
parameterType是传入这条语句的参数类的完全限定名或别名。这个属性是可选的
resultType从这条语句中返回的期望类型的类的完全限定名或别名。如果是集合情形,那应该是集合包含的类型,而不是集合本身。使用 resultType 或 resultMap,但不能同时使用。单表查询用resultType合适。
resultMap对于SQL语句查询出的字段在相应的pojo中必须有和它相同的字段对应,而resultMap中type的内容就是pojo在本项目中的位置。当使用resultMap做SQL语句返回结果类型处理时,通常需要在mapper.xml中定义resultMap进行pojo和相应表字段的对应。resultMap可以映射集合 resultType不可以。
resultSets这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。

2.insert, update 和 delete

数据变更语句 insert,update 和 delete 的实现非常接近:

id 可以用id来调用此语句,是命名空间的唯一标识符
parameterType 是传入这条语句的参数类的完全限定名或别名。这个属性是可选的
insert,update 和 delete 语句的示例:

<insert id="insertUser">
  insert into User(id,username,password,email)
  values (#{id},#{username},#{password},#{email})
</insert>
 
<update id="updateUser">
  update User set
    username = #{username},
    password = #{password},
    email = #{email},
  where id = #{id}
</update>
 
<delete id="deleteUser">
  delete from User where id = #{id}
</delete>

3.动态SQL

mybatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:

  1. if 语句 (简单的条件判断)

  2. choose (when,otherwize) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似.

  3. trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)

  4. where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)

  5. set (主要用于更新时)

  6. foreach (在实现 mybatis in 语句查询时特别有用)

下面分别介绍这几种处理方式:

(1)mybatis if语句处理
<select id="ariticleTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</select> 

解析:

如果你提供了title参数,那么就要满足title=#{title},同样如果你提供了Content和Owner的时候,它们也需要满足相应的条件,之后就是返回满足这些条件的所有Blog,这是非常有用的一个功能。

以往我们使用其他类型框架或者直接使用JDBC的时候, 如果我们要达到同样的选择效果的时候,我们就需要拼SQL语句,这是极其麻烦的,比起来,上述的动态SQL就要简单多了。

(2)choose (when,otherwize)

相当于java 语言中的 switch ,与 jstl 中的choose 很类似

<select id="dynamicChooseTest" parameterType="Blog" resultType="Blog">
 select * from t_blog where 1 = 1 
 <choose>
 <when test="title != null">
 and title = #{title}
 </when>
 <when test="content != null">
 and content = #{content}
 </when>
 <otherwise>
 and owner = "owner1"
 </otherwise>
 </choose>
</select>

解析:

when元素表示当when中的条件满足的时候就输出其中的内容,跟JAVA中的switch效果差不多的是按照条件的顺序,当when中有条件满足的时候,就会跳出choose,即所有的when和otherwise条件中,只有一个会输出,当所有的我很条件都不满足的时候就输出otherwise中的内容。所以上述语句的意思非常简单,当title!=null的时候就输出and titlte = #{title},不再往下判断条件,当title为空且content!=null的时候就输出and content = #{content},当所有条件都不满足的时候就输出otherwise中的内容。

(3)trim

对包含的内容加上 prefix前缀,或者 suffix后缀 等。

<select id="dynamicTrimTest" parameterType="Blog" resultType="Blog">
select * from t_blog 
<trim prefix="where" prefixOverrides="and | or">
<if test="title != null">
title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
or owner = #{owner}
</if>
</trim>
</select>

解析:

trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOverrides;正因为trim有这样的功能,所以我们也可以非常简单的利用trim来代替where元素的功能。

trim标记是一个格式化的标记,可以完成set或者是where标记的功能,如下代码:

select * from user 
<trim prefix="WHERE" prefixoverride="AND |OR">
<if test="name != null and name.length()>0"> 
AND name=#{name}
</if>
<if test="gender != null and gender.length()>0"> 
AND gender=#{gender}
</if>
</trim>

假如说name和gender的值都不为null的话打印的SQL为:select * from user where name = ‘xx’ and gender = ‘xx’

在AND name=#{name}是不存在第一个and的,上面两个属性的意思如下:

prefix:前缀

prefixoverride:去掉第一个and或者是or

update user
<trim prefix="set" suffixoverride="," suffix=" where id = #{id} ">
<if test="name != null and name.length()>0">
name=#{name} ,
</if>
<if test="gender != null and gender.length()>0">
gender=#{gender} , 
</if>
</trim>

假如说name和gender的值都不为null的话打印的SQL为:update user set name=‘xx’ , gender=‘xx’ where id=‘x’

在红色标记的地方不存在逗号,而且自动加了一个set前缀和where后缀,上面三个属性的意义如下,其中prefix意义如上:

suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)

suffix:后缀

(4)where

主要是用来简化sql语句中where条件判断的,能智能的处理 and or 条件

<select id="dynamicWhereTest" parameterType="Blog" resultType="Blog">
select * from t_blog 
<where>
<if test="title != null">
title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</where>
</select>

where元素的作用是会在写入where元素的地方输出一个where,另外一个好处是你不需要考虑where元素里面的条件输出是什么样子的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么MyBatis就会查出所有的记录,如果输出后是and 开头的,MyBatis会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略;此外,在where元素中你不需要考虑空格的问题,MyBatis会智能的帮你加上。像上述例子中,如果title=null, 而content != null,那么输出的整个语句会是select * from t_blog where content = #{content},而不是select * from t_blog where and content = #{content},因为MyBatis会智能的把首个and 或 or 给忽略。

(5)set -主要用于更新时
<update id="dynamicSetTest" parameterType="Blog">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="owner != null">
owner = #{owner}
</if>
</set>
where id = #{id}
</update>

set元素主要是用在更新操作的时候,它的主要功能和where元素其实是差不多的,主要是在包含的语句前输出一个set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。有了set元素我们就可以动态的更新那些修改了的字段。

(6)foreach-在实现 mybatis in 语句查询时特别有用

foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有item,index,collection,open,separator,close。

(1)item表示集合中每一个元素进行迭代时的别名。

(2)index指定一个名字,用于表示在迭代过程中,每次迭代到的位置。

(3)open表示该语句以什么开始。

(4)separator表示在每次进行迭代之间以什么符号作为分隔符。

(5)close表示以什么结束。

在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:

(1)如果传入的是单参数且参数类型是一个List的时候,collection属性值为list

(2)如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array

(3)如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key。

a.单参数List的类型
<select id="dynamicForeachTest" resultType="com.mybatis.entity.User">
select * from t_user where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>

上述collection的值为list,对应的Mapper是这样的:

/**mybatis Foreach测试 */
public List<User> dynamicForeachTest(List<Integer> ids);

测试代码:

@Test
public void dynamicForeachTest() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(6);
List<User> userList = mapper.dynamicForeachTest(ids);
for (User user : userList){
System.out.println(user);
}
sqlSession.close();
}
b.数组类型的参数
<select id="dynamicForeach2Test" resultType="com.mybatis.entity.User">
select * from t_user where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>

对应mapper:

public List<User> dynamicForeach2Test(int[] ids);

测试代码:

@Test
public void dynamicForeach2Test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int[] ids = {1,2,6};
List<User> userList = mapper.dynamicForeach2Test(ids);
for (User user : userList){
System.out.println(user);
}
sqlSession.close();
}
c.Map类型的参数
<select id="dynamicForeach3Test" resultType="com.mybatis.entity.User">
select * from t_user where username like '%${username}%' and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>

mapper 应该是这样的接口:

/**mybatis Foreach测试 */
public List<User> dynamicForeach3Test(Map<String, Object> params);

测试方法:

@Test
public void dynamicForeach3Test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(6);
Map map =new HashMap();
map.put("username", "小");
map.put("ids", ids);
List<User> userList = mapper.dynamicForeach3Test(map);
System.out.println("------------------------");
for (User user : userList){
System.out.println(user);
}
sqlSession.close();
}

二.拓展补充

1.@Param

(1)@Param介绍

@Param是MyBatis所提供的(org.apache.ibatis.annotations.Param),作为Dao层的注解,作用是用于传递参数,从而可以与SQL中的的字段名相对应,一般在2=<参数数<=5时使用最佳。

(2)原始的方法

当只有一个参数时,没什么好说的,传进去一个值也只有一个参数可以匹配。当存在多个参数时,传进去的值就区分不开了,这时可以考虑用Map,例如接口

public List<Role> findRoleByMap(Map<String, Object> parameter);

xml文件 ---------------(1)

<select id="findRoleByMap" parameterType="map" resultType="role">
    SELECT id,name FROM t_role
    WHERE roleName=#{roleName}
    AND note=#{note}
<select>

测试文件

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Map<String, Object> parameter = new HashMap<>();
parameter.put("roleName", "管理员");
parameter.put("note", "需要拓展业务");
List<Role> roles = roleMapper.findRolesByMap(parameter);
(3)使用@Param

很明显上面的缺点就在于可读性差,每次必须阅读他的键,才能明白其中的作用,并且不能限定其传递的数据类型,下面是使用@Param的情况,需要将接口改为

public List findRoleByAnnotation(@Param(“roleName”) String roleName, @Param(“note”) String note);
这样我们就可以直接传入对应的值了。

当然也可以使用Java Bean来传递多个参数,定义一个POJO

public class RoleParam {
private String roleName;
private String note;
/getter和setter/
}
此时接口就变为

public List findRoleByBean(RoleParam role);
这样对应的xml文件与1处的区别就在于id和parameterType发生了变化,id对应的方法和parameterType对应该类的权限定名。

而使用更多的场景可能是这样的,对应多个POJO

public List findRoleByMix(@Param(“roleP”) RoleParam role, @Param(“permissionP”) PermissionParam permission);

这样就可以进行如下映射

SELECT id,name FROM t_role WHERE roleName=#{roleP.roleName} AND note=#{rolep.note} AND level=#{permissionP.level}

注意此时并不需要写出parameterType属性,Mybatis会进行自动搜索。

2.resultMap

(1)resultMap 属性

constructor - 用于在实例化类时,注入结果到构造方法中
idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
arg - 将被注入到构造方法的一个普通结果
id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联可以指定为一个 resultMap 元素,或者引用一个
collection – 一个复杂类型的集合
嵌套结果映射 – 集合可以指定为一个 resultMap 元素,或者引用一个
discriminator – 使用结果值来决定使用哪个 resultMap
case – 基于某些值的结果映射
嵌套结果映射 – 一个 case 也是一个映射它本身的结果,因此可以包含很多相同的元素,或者它可以参照一个外部的 resultMap。

(2)resultMap用法实例

无参构造函数

角色实体类

@Data
@Alias("RoleDTO")
public class RoleDTO {
    private Long id;
    // 角色名称
    private String roleName;
    // 角色类型
    private Integer roleType;
    // 角色描述
    private String roleDesc;
    // 角色状态
    private Integer status;
    // 角色人数
    private Long roleCount;
    // 禁止删除标志
    private Integer forbidDel;
    // 角色人员
    private List<Long> userIds;
}

注意: 当前实体类是无参构造函数

Mapper文件

<resultMap id="RoleResultMap" type="RoleDTO">
    <id property="id" column="id"/>
    <result property="roleName" column="role_name"/>
    <result property="roleType" column="role_type"/>
    <result property="roleDesc" column="role_desc"/>
    <result property="status" column="status"/>
    <collection property="userIds" ofType="Long">
        <constructor>
            <arg column="user_id"/>
        </constructor>
    </collection>
</resultMap>

带参数构造函数

角色实体类

@Data
@Alias("RoleDTO")
public class RoleDTO {
    private Long id;
    // 角色名称
    private String roleName;
    // 角色类型
    private Integer roleType;
    // 角色描述
    private String roleDesc;
    // 角色状态
    private Integer status;
    // 角色人数
    private Long roleCount;
    // 禁止删除标志
    private Integer forbidDel;
    // 角色人员
    private List<Long> userIds;
 
    public RoleDTO(Long id, Integer status) {
        this.id = id;
        this.status = status;
    }
}

注意: 当前实体类的构造函数是带有id和status入参

Mapper文件

<resultMap id="RoleResultMap" type="RoleDTO">
    <!-- 注意实体类中只有有参数的构造方法, 此处需要匹配实体类中的构造方法 -->
   <constructor>
        <idArg column="id" javaType="Long" />
        <arg column="status" javaType="Integer" />
    </constructor>
    <result property="roleName" column="role_name"/>
    <result property="roleType" column="role_type"/>
    <result property="roleDesc" column="role_desc"/>
    <collection property="userIds" ofType="Long">
        <constructor>
            <arg column="user_id"/>
        </constructor>
    </collection>
</resultMap>

原文链接如有侵权联系删

3.关联查询

association通常用来进行关联查询,一般有两种使用方式。

(1)连接查询(推荐)

连接查询也就是在查询语句中使用 JOIN 关键字进行多表连接,然后用 association 进行映射。

<resultMap type="com.jeeplus.modules.entity.ProjectArea" id="projectArea"> 
    <id column="id" property="id" />
    <result column="project_id" property="projectId"/>
    <result column="area_name" property="areaName"/>
    <association property="responsibleUser" javaType="com.jeeplus.modules.sys.entity.User">
        <id column="responsible_user_id" property="id"/>
        <result column="responsible_user_name" property="name"/>
        <result column="responsible_user_mobile" property="mobile"/>
    </association>
</resultMap>
<select id="selectList" resultMap="projectArea">
    SELECT
        a.id                 AS 'id',
        a.project_id         AS 'project_id',
        a.area_name            AS 'area_name',
        u.id                AS 'responsible_user_id',
        u.name                 AS 'responsible_user_name',
        u.mobile            AS 'responsible_user_mobile'
    FROM
        project_area a LEFT JOIN
            sys_user u ON a.responsible_user_id = u.id
    <where>
        a.del_flag = '0'
        <if test="id != null and id != ''">
            AND a.id = #{id}
        </if>
        <if test="projectId != null and projectId != ''">
            AND a.project_id = #{projectId}
        </if>
        <if test="areaName != null and areaName != ''">
            AND a.area_name = #{areaName}
        </if>
    </where>
</select>
(2)分步查询

一般多表连接需要外键,在查询 A 表的结果,同时获得了关联表 B 的 ID (外键),我们只需要再通过 ID 查询 B 表就能获得 B 表信息。也就是多次查询。

这样做有一个特点,就是支持懒加载,如果开启了懒加载,那么不会立即进行查询。

注意:这只是代码层次的联表查询,如果注重性能的话还是选择第一种。

association 使用了一些新的属性,select 表示接口方法的全限定名,column 表示传递给方法的参数。

<resultMap type="com.jeeplus.modules.entity.ProjectArea" id="projectArea"> 
    <id column="id" property="id" />
    <result column="project_id" property="projectId"/>
    <result column="area_name" property="areaName"/>
    <association property="responsibleUser" select="com.jeeplus.modules.sys.mapper.UserMapper.getById" column="responsible_user_id">
        <id column="responsible_user_id" property="id"/>
        <result column="responsible_user_name" property="name"/>
        <result column="responsible_user_mobile" property="mobile"/>
    </association>
</resultMap>

参考链接 如有侵权联系删

4.缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。

默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:

<cache/>

字面上看就是这样。这个简单语句的效果如下:

映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
所有的这些属性都可以通过缓存元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。

可用的收回策略有:

LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

使用自定义缓存
除了这些自定义缓存的方式,你也可以通过实现你自己的缓存或为其他第三方缓存方案创建适配器来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示 例展 示了 如何 使用 一个 自定义 的缓 存实 现。type 属 性指 定的 类必 须实现org.mybatis.cache.Cache 接口。这个接口是 MyBatis 框架中很多复杂的接口之一,但是简单给定它做什么就行。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

要配置你的缓存,简单和公有的 JavaBeans 属性来配置你的缓存实现,而且是通过 cache元素来传递属性,比如,下面代码会在你的缓存实现中调用一个称为“setCacheFile(String file)”的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有简单类型作为 JavaBeans 的属性,MyBatis 会进行转换。

从3.4.2版本开始,MyBatis已经支持在所有属性设置完毕以后可以调用一个初始化方法。如果你想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {
  void initialize() throws Exception;
}

记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有在相同命名空间的语句正如绑定的缓存一样。语句可以修改和缓存交互的方式,或在语句的语句的基础上使用两种简单的属性来完全排除它们。默认情况下,语句可以这样来配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

因为那些是默认的,你明显不能明确地以这种方式来配置一条语句。相反,如果你想改变默认的行为,只能设置 flushCache 和 useCache 属性。比如,在一些情况下你也许想排除从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存。相似地,你也许有一些更新语句依靠执行而不需要刷新缓存。

参照缓存
这个特殊命名空间的唯一缓存会被使用或者刷新相同命名空间内的语句。也许将来的某个时候,你会想在命名空间中共享相同的缓存配置和实例。在这样的情况下你可以使用 cache-ref 元素来引用另外一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

三.Mybatis注解

1.mybatis简单注解

@Insert : 插入sql , 和xml insert sql语法完全一样

@Select : 查询sql, 和xml select sql语法完全一样
@Update : 更新sql, 和xml update sql语法完全一样
@Delete : 删除sql, 和xml delete sql语法完全一样
@Param : 入参
@Results : 设置结果集合

@Result : 结果
@ResultMap : 引用结果集合
@SelectKey : 获取最新插入id

具体使用:

(1)@Select、@Results、@Result
 

/**
* 查询所有
* @return Employee泛型集合
*/
@Select("select * from t_emp")
@Results(id = "empMap",value = {
@Result(column = "emp_id",property = "empId",id = true),
@Result(column = "emp_name",property = "empName"),
@Result(column = "emp_tel",property = "empTel"),
@Result(column = "emp_education",property = "empEducation"),
@Result(column = "emp_birthday",property = "empBirthday"),
@Result(column = "fk_dept_id",property = "dept"
,one = @One(select = "com.yingside.dao.DeptMapper.getById",
fetchType = FetchType.LAZY))
})
List<Employee> getAll();
(2)@Delete、@Param、@ResultMap
/**
* 根据id查询员工
* @param empId 员工主键id
* @return 员工对象
*/
@Select("select * from t_emp where emp_id=#{empId}")
@ResultMap(value="empMap")
Employee getById(@Param("empId") Integer empId);
(3)@Insert、@SelectKey
/**
* 插入新员工信息,并将最新id放入到员工对象在
* @param record 新员工对象
* @return 插入成功 1 失败 0
*/
@Insert("insert into t_emp (emp_id, emp_name, emp_tel, " +
" emp_education, emp_birthday, fk_dept_id" +
" )" +
" values (#{empId}, #{empName}, #{empTel}, " +
" #{empEducation}, #{empBirthday}, #{fkDeptId}" +
" )")
@SelectKey(before = false,keyColumn = "emp_id",keyProperty = "empId",
statement = "select last_insert_id()",resultType = Integer.class)
int insert(Employee record);
(4)@Delete、@Param
/**
* 根据员工id删除员工
* @param empId 员工主键id
* @return 删除成功 1 失败 0
*/
@Delete("delete from t_emp where emp_id=#{empId}")
int deleteByPrimaryKey(@Param("empId") Integer empId);
(5)@Update
/**
* 更新员工信息
* @param record 员工对象
* @return 更新成功 1 失败 0
*/
@Update("update t_emp" +
" set emp_name = #{empName,jdbcType=VARCHAR}," +
" emp_tel = #{empTel,jdbcType=VARCHAR}," +
" emp_education = #{empEducation,jdbcType=VARCHAR}," +
" emp_birthday = #{empBirthday,jdbcType=DATE}," +
" fk_dept_id = #{fkDeptId,jdbcType=INTEGER}" +
" where emp_id = #{empId,jdbcType=INTEGER}")
int update(Employee record);

当然最后别忘记了,写完这些之后,在核心配置文件中要把映射文件给加上,之前使用的时候,找寻的是resouce xml的资源路径,现在由于使用了注解,就只有接口文件了,所有配置相应的要做一个简单的修改

<mappers>
<mapper class="com.yingside.dao.EmployeeMapper" />
</mappers>

2.动态SQL

(1)简单处理,直接使用script脚本
/**
* script实现动态sql更新员工信息
* @param record 员工对象
* @return 更新成功 1 失败 0
*/
@Update("<script>" +
"update t_emp" +
" <set >" +
" <if test=\"empName != null\" >" +
" emp_name = #{empName,jdbcType=VARCHAR},\n" +
" </if>" +
" <if test=\"empTel != null\" >" +
" emp_tel = #{empTel,jdbcType=VARCHAR}," +
" </if>" +
" <if test=\"empEducation != null\" >" +
" emp_education = #{empEducation,jdbcType=VARCHAR}," +
" </if>" +
" <if test=\"empBirthday != null\" >" +
" emp_birthday = #{empBirthday,jdbcType=DATE}," +
" </if>" +
" <if test=\"fkDeptId != null\" >" +
" fk_dept_id = #{fkDeptId,jdbcType=INTEGER}," +
" </if>" +
" </set>" +
" where emp_id = #{empId,jdbcType=INTEGER}" +
"</script>")
int updateByPrimaryKeySelective(Employee record);
(2)使用Provider注解标识

增删改查,每一个都有一个对应的Provider注解标识

@Insert :@InsertProvider
@Select :@SelectProvider
@Update :@UpdateProvider
@Delete :@DeleteProvider
使用:

创建Provider类
public class EmployeeProvider {
public String updateSQL(Employee emp) {
return new SQL() {
{
UPDATE("t_emp");
if (emp.getEmpName() != null) {
SET("emp_name = #{empName}");
}
if (emp.getEmpTel() != null) {
SET("emp_tel = #{empTel}");
}
if (emp.getEmpEducation() != null) {
SET("emp_education = #{empEducation}");
}
if (emp.getEmpBirthday() != null) {
SET("emp_birthday = #{empBirthday}");
}
if (emp.getFkDeptId() != null) {
SET("fk_dept_id = #{fkDeptId}");
}
WHERE("emp_id = #{empId}");
}
}.toString();
}
}

这里使用的Provider的一些关键字

掌握最重要的一点,Provider其实就是要返回一个SQL字符串 只不过用了一些关键字做格式化而已,其实不使用也可以 完全可以使用String字符串拼接SQL语句,当然复杂的语句还是建议使用StringBuilder或者StringBuffer拼接

注解使用Provider类
@UpdateProvider(type = EmployeeProvider.class, method = "updateSQL")
int updateBySelectiveProvider(Employee record);

3.注解使用多表关联查询

(1)使用子查询方式
多对一或者一对一中的一端写法 @One

这种查询方式和之前在xml中介绍的第二种方式一样,相当于就是一个子查询。先查询员工,再根据员工查询出来的部门外键查询部门信息,最后再把查询出来的信息放入到Employee对象中的Dept属性中

EmployeeMapper.java 接口:

/**
* 查询所有
* @return Employee泛型集合
*/
@Select("select * from t_emp")
@Results(id = "empMap",value = {
@Result(column = "emp_id",property = "empId",id = true),
@Result(column = "emp_name",property = "empName"),
@Result(column = "emp_tel",property = "empTel"),
@Result(column = "emp_education",property = "empEducation"),
@Result(column = "emp_birthday",property = "empBirthday"),
@Result(column = "fk_dept_id",property = "dept"
,one = @One(select = "com.yingside.dao.DeptMapper.getById",
fetchType = FetchType.LAZY))
})
List<Employee> getAll();
DeptMapper.java 接口:

public interface DeptMapper {
@Results(id="deptMap",value = {
@Result(column = "dept_id",property = "deptId",id = true),
@Result(column = "dept_name",property = "deptName"),
@Result(column = "dept_info",property = "deptInfo"),
@Result(column = "dept_createDate",property = "deptCreatedate")
})
@Select("select * from t_dept where dept_id=#{deptId}")
Dept getById(@Param("deptId") Integer deptId);
}
一对多或者多对多中的多端写法 @Many

通过子查询,先查询部门信息,再通过部门主键,在员工外键表中查询和部门相关联的员工信息
DeptMapper.java 接口:

public interface DeptMapper {
@Results(id="deptMap",value = {
@Result(column = "dept_id",property = "deptId",id = true),
@Result(column = "dept_name",property = "deptName"),
@Result(column = "dept_info",property = "deptInfo"),
@Result(column = "dept_createDate",property = "deptCreatedate"),
@Result(column = "dept_id",property = "employeeList"
,many = @Many(select = "com.yingside.dao.EmployeeMapper.getEmpsByDeptId",
fetchType = FetchType.LAZY))
})
@Select("select * from t_dept where dept_id=#{deptId}")
Dept getById(@Param("deptId") Integer deptId);
}
EmployeeMapper.java 接口:

public interface EmployeeMapper {
//......其他代码省略
/**
* 根据部门id查询部门下所有员工
* @param fkDeptId 部门id
* @return 员工的泛型集合
*/
@Select("select * from t_emp where fk_dept_id=#{fkDeptId}")
//@ResultMap("empMap")
List<Employee> getEmpsByDeptId(@Param("fkDeptId") int fkDeptId);
}
(2)N+1问题

注意:像上面这种通过子查询的方式实现关联查询,最好就只是查询一个方向就行了,意思是,要么通过员工查询部门,要么就通过部门查询员工。不要两个都写。上面的例子只是简单说明一端和多端的写法。如果两个都写的话,就会引起循环引用的问题,查员工的时候发现要子查询部门,查了部门发现又要查员工…就会报出下面的错误:

java.lang.StackOverflowError
所以,最佳实践:尽量不要出现这种情况的互相引用。这种子查询的方式,建议是在必须使用懒加载的情况下使用。如果一般情况的多表查询,还是使用表的级联分页查询,inner,left,right join等

(3)最佳实践-注解与xml结合使用

3.1.xml文件配置resultMap,这样方便多个接口调用

3.2.配置映射

3.3.注解代码

3.3.1.简单的注解代码

/**
* 通过级联查询查询员工信息
* @return 员工的泛型集合
*/
@Select("select * from t_emp join t_dept on t_emp.fk_dept_id = t_dept.dept_id")
@ResultMap("com.yingside.mapper.EmployeeMapper.employeeMap")
List<Employee> getAllJoin();

注意这里@ResultMap的值是xml文件的地址

3.3.2.复杂的代码可以结合使用Provider

EmployeeProvider.java
 
 
package com.yingside.provider;
import com.yingside.po.Employee;
import org.apache.ibatis.jdbc.SQL;
import java.util.Map;
public class EmployeeProvider {
//......其他代码省略
public String selectListProvider(Map<String,Object> map){
return new SQL(){
{
SELECT(" t_emp.emp_id," +
" t_emp.emp_name," +
" t_emp.emp_tel," +
" t_emp.emp_education," +
" t_emp.emp_birthday," +
" t_dept.dept_id," +
" t_dept.dept_name," +
" t_dept.dept_info," +
" t_dept.dept_createDate");
FROM("t_emp");
LEFT_OUTER_JOIN("t_dept on t_emp.fk_dept_id = t_dept.dept_id");
if (map.get("empName") != null){
AND().WHERE("t_emp.emp_name like concat('%',#{empName},'%')");
}
if (map.get("empTel") != null){
AND().WHERE("t_emp.emp_tel like concat('%',#{empTel},'%')");
}
if (map.get("deptId") != null){
AND().WHERE("t_dept.dept_id=#{deptId}");
}
}}.toString();
}
}
EmployeeDao.java

/**
* 通过级联查询动态sql Provider查询员工新
* @param map 键值对的参数
* @return 员工的泛型集合
*/
@SelectProvider(type = EmployeeProvider.class, method = "selectListProvider")
@ResultMap("com.yingside.mapper.EmployeeMapper.employeeMap")
List<Employee> getAllJoinProvider(Map<String,Object> map);
测试

@Test
public void testGetAllJoinProvider(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("empName","公");
map.put("deptId",1);
List<Employee> all = mapper.getAllJoinProvider(map);
log.info(all);
sqlSession.commit();
}

参考链接如有侵权联系删
————————————————
版权声明:本文为CSDN博主「LikM_1100」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/L_994572281_LYA/article/details/118413483

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值