06月28, 2019

MyBatis手把手跟我做(八) Mybatis注解

MyBatis(八) mybatis注解

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

二、动态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

使用:

2.1、创建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拼接

2.2、注解使用Provider类

@UpdateProvider(type = EmployeeProvider.class, method = "updateSQL")
int updateBySelectiveProvider(Employee record);

三、注解使用多表关联查询

1、使用子查询方式

1.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);
}

1.2、一对多或者多对多中的多端写法 @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();
}

四、Mybatis全部注解列表

注解 目标 相对应的 XML 描述
@CacheNamespace <cache> 为给定的命名空间 (比如类) 配置缓存。 属性:implemetation,eviction, flushInterval,size 和 readWrite。
@CacheNamespaceRef <cacheRef> 参照另外一个命名空间的缓存来使用。 属性:value,应该是一个名空间的字 符串值(也就是类的完全限定名) 。
@ConstructorArgs 方法 <constructor> 收集一组结果传递给一个劫夺对象的 构造方法。属性:value,是形式参数 的数组。
@Arg 方法 <arg>``<idArg> 单 独 的 构 造 方 法 参 数 , 是 ConstructorArgs 集合的一部分。属性: id,column,javaType,typeHandler。 id 属性是布尔值, 来标识用于比较的属 性,和XML 元素相似。
@TypeDiscriminator 方法 <discriminator> 一组实例值被用来决定结果映射的表 现。 属性: column, javaType, jdbcType, typeHandler,cases。cases 属性就是实 例的数组。
@Case 方法 <case> 单独实例的值和它对应的映射。属性: value,type,results。Results 属性是结 果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
@Results 方法 <resultMap> 结果映射的列表, 包含了一个特别结果 列如何被映射到属性或字段的详情。 属 性:value, id。value 属性是 Result 注解的数组。 The id attribute is the name of the result mapping.
@Result 方法 <result>``<id> 在列和属性或字段之间的单独结果映 射。属 性:id,column, property, javaType ,jdbcType ,type Handler, one,many。id 属性是一个布尔值,表 示了应该被用于比较(和在 XML 映射 中的相似)的属性。one 属性是单 独 的 联 系, 和 <association> 相 似 , 而 many 属 性 是 对 集 合 而 言 的 , 和 <collection>相似。 它们这样命名是为了 避免名称冲突。
@One 方法 <association> 复杂类型的单独属性值映射。属性: select,已映射语句(也就是映射器方 法)的完全限定名,它可以加载合适类 型的实例。注意:联合映射在注解 API 中是不支持的。这是因为 Java 注解的 限制,不允许循环引用。 fetchType, which supersedes the global configuration parameterlazyLoadingEnabled for this mapping.
@Many 方法 <collection> A mapping to a collection property of a complex type. Attributes: select, which is the fully qualified name of a mapped statement (i.e. mapper method) that can load a collection of instances of the appropriate types,fetchType, which supersedes the global configuration parameterlazyLoadingEnabled for this mapping. NOTE You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references.
@MapKey 方法 复 杂 类 型 的 集合 属 性 映射 。 属 性 : select,是映射语句(也就是映射器方 法)的完全限定名,它可以加载合适类 型的一组实例。注意:联合映射在 Java 注解中是不支持的。这是因为 Java 注 解的限制,不允许循环引用。
@Options 方法 映射语句的属性 这个注解提供访问交换和配置选项的 宽广范围, 它们通常在映射语句上作为 属性出现。 而不是将每条语句注解变复 杂,Options 注解提供连贯清晰的方式 来访问它们。属性:useCache=true , flushCache=FlushCachePolicy.DEFAULT , resultSetType=FORWARD_ONLY , statementType=PREPARED , fetchSize=-1 , , timeout=-1 useGeneratedKeys=false , keyProperty=”id” , keyColumn=”” , resultSets=””。 理解 Java 注解是很 重要的,因为没有办法来指定“null” 作为值。因此,一旦你使用了 Options 注解,语句就受所有默认值的支配。要 注意什么样的默认值来避免不期望的 行为。 @Insert``@Update``@Delete``@Select 方法 <insert>``<update>``<delete>``<select> 这些注解中的每一个代表了执行的真 实 SQL。 它们每一个都使用字符串数组 (或单独的字符串)。如果传递的是字 符串数组, 它们由每个分隔它们的单独 空间串联起来。这就当用 Java 代码构 建 SQL 时避免了“丢失空间”的问题。 然而,如果你喜欢,也欢迎你串联单独 的字符串。属性:value,这是字符串 数组用来组成单独的 SQL 语句。 @InsertProvider``@UpdateProvider``@DeleteProvider``@SelectProvider 方法 <insert>``<update>``<delete>``<select> 这些可选的 SQL 注解允许你指定一个 类名和一个方法在执行时来返回运行 允许创建动态 的 SQL。 基于执行的映射语句, MyBatis 会实例化这个类,然后执行由 provider 指定的方法. 该方法可以有选择地接受参数对象.(In MyBatis 3.4 or later, it’s allow multiple parameters) 属性: type,method。type 属性是类。method 属性是方法名。 注意: 这节之后是对 类的 讨论,它可以帮助你以干净,容于阅读 的方式来构建动态 SQL。
@Param Parameter 如果你的映射器的方法需要多个参数, 这个注解可以被应用于映射器的方法 参数来给每个参数一个名字。否则,多 参数将会以它们的顺序位置来被命名 (不包括任何 RowBounds 参数) 比如。 #{param1} , #{param2} 等 , 这 是 默 认 的 。 使 用 @Param(“person”),参数应该被命名为 #{person}。
@SelectKey 方法 <selectKey> This annotation duplicates the functionality for methods annotated with @Insert, @InsertProvider, @Update or@UpdateProvider. It is ignored for other methods. If you specify a@SelectKey annotation, then MyBatis will ignore any generated key properties set via the @Options annotation, or configuration properties. Attributes: statement an array of strings which is the SQL statement to execute, keyProperty which is the property of the parameter object that will be updated with the new value, before which must be either true orfalse to denote if the SQL statement should be executed before or after the insert, resultType which is the Java type of the keyProperty, andstatementType=PREPARED.
@ResultMap 方法 This annotation is used to provide the id of a <resultMap> element in an XML mapper to a @Select or @SelectProvider annotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any @Results or @ConstructorArgs annotation if both are specified on an annotated select.
@ResultType 方法 This annotation is used when using a result handler. In that case, the return type is void so MyBatis must have a way to determine the type of object to construct for each row. If there is an XML result map, use the @ResultMap annotation. If the result type is specified in XML on the<select> element, then no other annotation is necessary. In other cases, use this annotation. For example, if a @Select annotated method will use a result handler, the return type must be void and this annotation (or @ResultMap) is required. This annotation is ignored unless the method return type is void.
@Flush 方法 If this annotation is used, it can be called theSqlSession#flushStatements() via method defined at a Mapper interface.(MyBatis 3.3 or above)

本文链接:http://www.yanhongzhi.com/post/mybatis-annotation.html

-- EOF --

Comments