mybatis映射文件

mybatis的映射文件使用与书写操作数据库的SQL语句的,下面进行详细的了解

insert标签

参数介绍:

  • id:给当前insert标签一个唯一标识
  • parameterType:参数类型,可以省略不写,mybatis会根据TypeHandler自动推测

例:

<insert id="setValue" parameterType="com.jinxin.bean.Employee">
    INSERT INTO tb1_employee(last_name, gender, email)
    VALUES(#{lastName}, #{email}, #{gender})
</insert>

#{}里面可以直接写实体类的属性名

测试:

@Test
public void test2() throws IOException {
    String resource = "data/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = sqlSessionFactory.openSession();
    try {
        EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
        Employee employee = new Employee();
        employee.setGender(0);
        employee.setLastName("Jerry");
        employee.setEmail("123@123.com");
        mapper.setValue(employee);
        session.commit();
    } finally {
        session.close();
    }
}

注意:

  1. 此时的sqlSession是不会自动提交的,需要我们手动的commit到数据库,如果想要自动提交,在生成sqlSession的时候传入true参数即可,即sqlSessionFactory.openSession(true)

  2. 对于增删改,我们可以为方法设置返回,mybatis支持IntegerBoolean(boolean)Long,void等返回值,针对返回值的类型不同会自动封装,如果是IntegerLong,会返回受影响的行数,如果是Boolean(boolean),则如果受影响的行数大于0,那么就返回false,如果不为0返回true

insert获取自增主键

在JDBC中通过statement的一个方法statement.getGenreateKeys()即可获得主键,在mybatis中同样使用了该方法获取主键,想要获取主键,只需要在insert标签中将useGeneratedKeys设置为true,然后使用keyProperty设置指定对应的主键属性,也就是mybatis获取到主键以后,将这个值封装给javaBean的哪个属性

例:

<insert id="setValue" parameterType="com.jinxin.bean.Employee"
useGeneratedKeys="true" keyProperty="id">
  INSERT INTO tb1_employee(last_name, gender, email)
  VALUES(#{lastName}, #{gender}, #{email})
</insert>

不支持自增主键的数据库

针对能够自增的属性,可以使用上面所说的两个属性获取主键,但是对于不支持自增主键的数据库,就不能这样做了,以oracle为例,需要使用序列模拟自增,每次插入值的数据的主键是从序列中拿到的,那么应该怎样在mapper文件中获取呢?

<insert id="setValue" parameterType="com.jinxin.bean.Employee">
  <!--
    keyProperty:查出的主键封装给javaBean的某个属性
    order="BEFORE":当前sql在插入的sql之前运行
          "AFTER":当前sql在插入的sql之后运行
    resultType:查出的数据的返回值类型,这里的Integer是别名
  -->
    <selectKey keyProperty="id" order="BEFORE" resultType="Integer">
      <!-- 编写主键的sql句 -->
      SELECT EMPLOYEE_SEQ.nextval FROM dual
    </selectKey>
    <!-- 插入时的主键是从序列中拿到的 -->
    INSERT INTO tb1_employee(id, last_name, gender, email)
    VALUES(#{id}, #{lastName}, #{gender}, #{email})
</insert>

当然,也可以不用先将查到的序列放到javaBean中,直接在SQL中查到序列的下一个值,然后将查询序列的SQLorder设置为AFTER,将当前的序列值放到javaBean

<insert id="setValue" parameterType="com.jinxin.bean.Employee">
    <selectKey keyProperty="id" order="AFTER" resultType="Integer">
      <!-- 编写主键的sql句 -->
      SELECT EMPLOYEE_SEQ.currval FROM dual
    </selectKey>
    <!-- 插入时的主键是从序列中拿到的 -->
    INSERT INTO tb1_employee(id, last_name, gender, email)
    VALUES(EMPLOYEE_SEQ.nextval, #{lastName}, #{gender}, #{email})
</insert>

update标签

更新所选数据

例:

<update id="updateValue">
    UPDATE tb1_employee
    SET last_name=#{lastName}, email=#{email}, gender=#{email}
    WHERE id=#{id}
</update>

delete标签

删除所选数据

例:

<delete id="deleteValue">
    DELETE FROM tb1_employee WHERE id=#{id]}
</delete>

select标签

 属性介绍

  • id:给标签一个唯一的标识
  • resultTye:返回值类型
  • resultMap:自定义返回值类型

例:

<select id="getById" resultType="com.jinxin.bean.Employee">
  SELECT * FROM `tb1_employee` WHERE id = #{id}
</select>

返回结果处理

从数据库查询的结果可能多种多样,需要我们用不同的方式去接收,有些甚至需要自定义接收规则(resultMap)

下面一一列举

select返回list

如果当查询的结果是多条数据,就可以使用List接收返回的结果,但是这里的resultType并不是list,而是集合里面储存的元素的类型,例如,这是一条查询多个员工的语句,那么resultType里面写的应该是员工对应的实体类,而并非list

例:

<select id=”getEmpByLastName” resultType=”com.jinxin.bean.Employee”>
    SELECT * FROM tb1_employee WHERE last_name LIKE #{lastName}
</select>

select返回map

使用map储存数据的情况分为两种。一种是查询的结果只有一条,但是将这一条数据的每一列的列名跟值都以键值对的情况去保存在map中。第二种是查询的结果是多条,每一条数据为一个单位存在于value中,至于键的名字,以某一列的值做为键,一般是id,因为map的键名不能重复

单条数据封装成map

只需要将resultType的只设置为map即可

例:

<select id=”getEmpByIdReturnMap” resultType=”map”>
    SELECT * FROM tb1_employee WHERE id=#{id}
</select>

多条数据封装成map

这时需要改变的是mapper接口的方法,返回值类型为 Map<String, Employee> ,而在select标签上面的resultType仍然书写为对应实体类的全类名

即:

<select id=”getEmpByLastName” resultType=”com.jinxin.bean.Employee”>
    SELECT * FROM tb1_employee FROM WHERE last_name LIKE #{lastName}
</select>

但是还有一点,还需要在mapper接口的方法上标注上@MapKey注解,指明以那一列的数值作为键,如:

@MapKey(“id”)
Map<Integer, Employee> getEmpByLastName(String lastName);

就是以id的值作为键

使用resultMap自定义结果集映射规则

有的时候,对于返回的结果并不是一个实体类或者map能够全部接收搞定的,它们更加的复杂,往往需要我们自定义结果集的映射规则,而自定义结果集映射规则用到的一个很重要的标签就是resultMap

这里先举一个小例子说明resultMap的好处,例如如果表的列名跟实体类的属性名不能够完美的匹配,那么可以使用resultMap来做一一映射

例:

<mapper namespace="com.jinxin.mapper.EmployeeMapperPlus">
    <!--
        自定义某个实体类的封装规则
        type:实体类名
        id:给一个唯一标识
    -->
    <resultMap id="MyEmp" type="com.jx.bean.Employee">
        <!-- 指定主键的封装规则,也可以使用result定义主键,只是使用id定义主键mybatis会在底层有优化
            column:指定表的列名
            property:指定实体类对应的属性名
         -->
        <id column="id" property="id" />
        <!-- 定义普通列的封装规则 -->
        <result column="last_name" property="lastName" />
        <!-- 其他不指定的列会自定封装(列名跟属性名相同),但是推荐只要定义了resultType就把所有的对应关系写上 -->
        <result column="email" property="email" />
        <result column="gender" property="gender" />
    </resultMap>

    <select id="getEmpById" resultMap="MyEmp">
        SELECT * FROM tb1_employee WHERE id=#{id}
    </select>
</mapper>    
View Code

当然了,这里只是一个小例子,并不能体现resultMap真正的强大之处。resultMap真正的强大之处在于能够处理外键关联的对象,试想,如果一个实体类中有一个属性是另外一个实体类,但是在表中只是以外键的形式将两张表进行关联,也就是表中与这个实体类属性关联的列只是一个外键,一个数字,那么这时候用简单点的resultType处理肯定就不合适了,一个数字这么赋值给一个实体类对象?

回到这个问题,如果一个实体类中有一个关联的实体类,那么怎么赋值?首先明确应该怎样发送SQL查询,第一种方式可以使用连表查询将所有的数据一起查出来。还有一种方式,先查出一条数据,获取外键,然后去关联的表查询与这条外键对应的列

首先说说连表查询怎样封装resultMap

处理下面这个场景

如果每个员工都对应一个部门,现在要查询每个员工的同时查询员工对应的部门信息

<resultMap id="MyDifEmp" type="com.jinxin.bean.Employee">
    <id column="eid" property="id" />
    <result column="last_name" property="lastName" />
    <result column="gender" property="gender" />
    <result column="email" property="email" />
    <result column="fk" property="department.id" />
    <result column="dept_name" property="department.departmentName" />
</resultMap>
<select id="getEmpAndDept" resultMap="MyDifEmp">
  SELECT
    e.id eid, e.last_name last_name, e.gender gender, e.email email, e.d_id fk, d.id did, d.dept_name dept_name
  FROM
    tb1_employee e
  INNER JOIN
    tb1_dept d
  ON
    e.d_id = d.id
  WHERE
    e.id=#{id}
</select>

association

上面封装另外一个实体类的时候,使用的是 属性.属性 的方式对department里面的id跟dedpartmentName进行赋值,其实还有一种方式,便是通过association赋值

例:

<resultMap id="MyDifEmp2" type="com.jinxin.bean.Employee">
    <id column="eid" property="id" />
    <result column="last_name" property="lastName" />
    <result column="gender" property="gender" />
    <result column="email" property="email" />
    <!--
        property:指定关联的另外一个实体类
        javaType:指定该实体类的类型[不能省略]
    -->
    <association property="department" javaType="com.jinxin.bean.Department">
        <id column="did" property="id" />
        <result column="dept_name" property="departmentName" />
    </association>
</resultMap>

<select id="getEmpAndDept" resultMap="MyDifEmp">
  SELECT
    e.id eid, e.last_name last_name, e.gender gender, e.email email, e.d_id fk, d.id did, d.dept_name dept_name
  FROM
    tb1_employee e
  INNER JOIN
    tb1_dept d
  ON
    e.d_id = d.id
  WHERE
    e.id=#{id}
</select>

无论是使用这种属性链属性的方式封装还是使用association的方式封装都是使用连表查询先查询出两张表的所有数据,然后再对实体类进行一一对应的赋值,那么接下来介绍另外一种方式,先查询一张表的数据,再根据其外键查询另外一张表的数据

使用association进行分布查询

首先明确分步查询的步骤:

  1. 先按照员工id查询员工信息
  2. 再按照员工信息中的d_id外键查询员工对应的部门信息
  3. 将查询到的部门信息放置到员工信息中

Okay,那么首先明确resultMap封装流程,首先还是通过id跟result等标签封装当前实体类的普通属性,然后在association中,如先前一样,还是在里面使用id和result标签封装另外一个实体类相关的属性,但是这里多了一步,就是需要通过外键先查询,而且这个查询的方法已经在另一个实体类对应的mapper接口中定义好了,现在要做的只是拿到这个方法,而且将主键值作为参数传递给这个方法去执行,后面association就能帮我们自动封装查询出来的方法了。那么完成拿到这个方法这一任务的属性就是select,而完成将外键值传给方法这一任务的属性就是column。那么现在看一个栗子

<resultMap id="getEmp" type="com.jinxin.bean.Employee">
    <id column="id" property="id" />
    <result column="last_name" property="lastName" />
    <result column="email" property="email" />
    <result column="gender" property="gender" />
    <!-- 定义关联对象的封装规则
        select:因为要在这里面通过外键查询另一张表的数据,所以要指定通过外键查询的方法
        column:给select中指定的方法传入参数,即外键值
        流程:使用select属性指定的mapper接口的方法(参数是column对应的值)查出对象,并封装给property指定的属性
    -->
    <association property="department" select="com.jinxin.mapper.DepartmentMapper.getDeptById" column="d_id">
    </association>
</resultMap>
<select id="getEmpAndDeptStep" resultMap="getEmp">
    SELECT * FROM tb1_employee WHERE id=#{id}
</select>

分布查询之懒加载

上面的查询每次进行查询都会去数据库一次性查询到所有的信息,为了减轻数据库的压力,我们可以使用延迟加载,在每次用到的时候再去查询数据库拿到需要的信息,即按需加载。

使用延迟加载非常简单,只需要在分步查询的基础上在主配置文件中加上两个setting配置即可

<settings>
    <setting name="lazyLoadingEnabled" value="true" />
    <setting name="aggressiveLazyLoading" value="false" />
</settings>

在全局设置了懒加载就意味着所有的查询都是懒加载,如果我们在某一项查询中不需要使用懒加载可以使用fetchType覆盖掉全局的设置,它有两个值,默认是lazy(延迟加载),跟eager(立即加载)

collection

其实上面说了这么多,本质上是在使用resultMap封装一对多。那么上面已经解决了一对多多的一方的封装,下面自然就要解决一对多一的一方的封装了。但是一的一方持有的不再是单个实体类了,而是所有与之关联的实体类的集合,用List保存,那么在resultMap中又应该用什么方式封装这个List结果集呢?根据标题可以知道,使用collection,下面就具体介绍这个collection的用法

现在有这样一个场景,根据某个id查询出一条部门信息,并且查询出所有与该id进行外键关联了的员工

很明显,同样有两种方式去做,一种是通过连表查询,直接查出与该id相等的外键有哪些。第二种方式是先查出这个部门,然后根据这个部门去员工表中匹配外,拿到所有属于该部门的员工信息

首先还是说说连表查询的封装规则

其实同association的封装规则很相似,collection里面还是用id跟result封装普通的属性,仍然是用property指定属性名,使用column指定列名。对于collection的属性,有一个ofType是用来指定List里面储存的实体类的类型。property用来指定这个列表的名字

如下:

<resultMap id="getDept" type="com.jinxin.bean.Department">
    <id column="did" property="id" />
    <result column="dept_name" property="departmentName" />
    <!-- collection定义关联集合类型的属性的封装规则
        ofType:指定集合里面的实体类的类型
    -->
    <collection property="emps" ofType="com.jinxin.bean.Employee">
        <!-- 定义集合中的实体类的封装规则 -->
        <id column="eid" property="id" />
        <result column="last_name" property="lastName" />
        <result column="email" property="email" />
        <result column="gender" property="gender" />
    </collection>
</resultMap>
<select id="getDeptByIdPlus" resultMap="getDept">
    SELECT
      d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email, e.gender gender
    FROM
      tb1_dept d
    LEFT JOIN
      tb1_employee e
    ON
      d.id=e.d_id
    WHERE
      d.id=#{id}
</select>

 使用collection进行分布查询

上面说了连表查询的方式,下面使用分布查询的方式查询数据

select属性同样是传入已经写好的通过外键查员工的方法,column同样是将部门的id传给该方法

<resultMap id="getDept2" type="com.jinxin.bean.Department">
    <id column="id" property="id" />
    <result column="dept_name" property="departmentName" />
    <collection property="emps" select="com.jinxin.mapper.EmployeeMapperPlus.getEmpByDeptId" column="id"></collection>
</resultMap>
<select id="getDeptAndEmpStep" resultMap="getDept2">
  SELECT * FROM tb1_dept WHERE id=#{id}
</select>
懒加载

collection的懒加载设置跟association的一致

扩展:association跟collection传入多个值

上面的分步查询使用到的方法都只需要传递一个值,那么如果涉及到的方法要传入多个值怎么办呢?

如果要传递的值是多个,可以通过map传递,column=”{key1=column1, key2=column2}”,即column=”{deptId=id}”

discriminator

 鉴别器,mybatis可以使用鉴别器判断某列的值,然后根据这一列的值改变封装行为

例:

封装Employee:

要求:如果查询的是女生,就把其部门信息查询出来,否则不查询其部门信息,而且如果是男生,就把last_name这一列赋值给email

<resultMap id="getEmp" type="com.jinxin.bean.Employee">
    <id column="id" property="id" />
    <result column="last_name" property="lastName" />
    <result column="email" property="email" />
    <result column="gender" property="gender" />
    <!--
        column:指定判断的列的列名
        javaType:列对应的java类型
    -->
    <discriminator javaType="string" column="gender">
        <!-- 女生
            resultType(必须):指定封装结果的类型,也可以指定resultMap
        -->
        <case value="0" resultType="com.jinxin.bean.Employee">
            <association property="department" select="com.jinxin.mapper.DepartmentMapper.getDeptById" column="d_id">
            </association>
        </case>
        <!-- 男生 -->
        <case value="1" resultType="com.jinxin.bean.Employee">
            <id column="id" property="id" />
            <result column="last_name" property="lastName" />
            <result column="last_name" property="email" />
            <result column="gender" property="gender" />
        </case>
    </discriminator>
</resultMap>
<select id="getEmpAndDeptStep" resultMap="getEmp">
    SELECT * FROM tb1_employee WHERE id=#{id}
</select>

mapper接口方法的参数处理

单个参数

对于单个参数,mybatis不会做特殊处理,使用 #{参数名] 即可取出参数值

多个参数

mybatis在操作多个参数时会做特殊的处理,多个参数会被封装到一个map里面,也就是{param1: id, param2: lastName}的形式

#{} 也是从map中通过key获取相应的值,也就是说如果传入的是多个参数,要使用#{param1}的形式才能取到值,或者使用参数的索引也可以取到值,例如 #{0}

命名参数

如果按照上面使用param1、param2...进行取值确实不方便,不但low,而且参数之间也不便于区分,这时可以使用命名参数显示的指定key的值,也就是将param1的换成自定义的名字

而自定义命名参数的方法也很简单,只需要在对应的方法前面加上@Param注解即可

Employee getByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);

这样就可以使用 #{id} 跟 #{lastName} 取值了

使用POJO & Map & TO(Transfer Object)

POJO

如果多个参数正好是我们业务逻辑中的数据模型,那我们就可以直接传入相应的pojo

使用#{属性名}即可取出相应pojo的属性值

Map

如果多个参数不是业务模型中的数据,没有对应的pojo,且不经常使用,那么为了方便也可以传入map,使用#{key}即可取出map中的值

To

如果多个数据不是业务逻辑中的数据模型,且要经常使用,那么推荐编写一个TO(Transfer Object)数据传输对象,例如查分页的Page对象

特别注意

如果封装的对象是一个CollectionList, Set)类型或者数组,也会进行处理,也就是把传入的值放到map中,如果是Collection,那么key就是collection,如果是List除了使用collection以外还可以使用list,如果是数组,key就是array

即:Employee getById(List<Integer> ids);

取值:#{list[0]}

参数取值

除了可以使用#{}取值以外,还可以使用${}取值,那么这两种取值方式有什么差别呢?其实在取值的时候还真没有什么差别,唯一的差别就是:

#{}是以预编译的方式将参数设置到sql中,即相当于JDBC中的PreparedStatement

${}取出的值直接拼装在sql语句中,有安全问题,因此大多数情况下应该使用#{}取值

但是在jdbc不支持预编译的地方可以使用${}进行取值,例如我想要查找的表是动态的,即有2016_salary2017_salary等等,那么就可以使用${year}_salary进行取值

#{}更丰富的用法

规定参数的一些规则:

javaTypejdbcTypemode(存储过程)numericScaleresultMaptypeHandlerjdbcTypeNameexpression(未来准备支持的功能)

 

jdbcType通常需要在某种特定的条件下被设置,有些数据库可能不能识别mybatisnull的默认处理,比如oracle(报错),如果想要保存一个值为null的字段会报错(jdbcType OTHER),因为mybatis对所有的null都映射的是原生JdbcOTHER类型,oracle不能正确处理

 

解决这种错误的方法有两种

1、对该插入为null的字段指定jdbcType#{email, jdbcType=NULL}

2、更改全局配置jdbcTypeForNull = NULL  <setting name=”jdbcTypeForNull” value=”NULL” />

 

posted @ 2018-11-10 18:54  Jin同学  阅读(106)  评论(0)    收藏  举报