映射器知识点总结

前言

        在MyBatis中,映射器是一个强大的工具,也是一个核心工具,所有的操作都离不开映射器。MyBatis是针对映射器构造的SQL构建的轻量级框架,通过映射器,我们可以将查询结果映射成一个JavaBean返回给调用者。通过映射器我们可以完成对数据的增、删、改、查操作,其中难点在于结果集映射缓存。接下来我们进行详细介绍

1.映射器主要元素

首先通过一张表格来展示映射器的基本元素:

c27c5f61646490b143478044fb4a945e503.jpg

1.1 select元素

        select元素是我们常用的SQL语言,我们通过它从数据库里面读取数据,select通过如下元素的配置实现其强大的功能:

        dc35ef10609cee12081d65bf089db937d50.jpg

        c122b7d7d83853d7e03c620fa2ec4e069f5.jpg

        上面的表格列出了所有的配置,我们可以根据需求应用不同的配置,接下来将介绍关键的配置

        一个简单的实例

            mapper.xml中的配置

<select id="countFirstName" parameterType="string"
		resultType="int">
		select count(1) from t_user where user_name like
		concat(#{firstName},'%')
</select>

            接口如下:

public int countFirstName(String firstname);

            分析:

            a、id标识了这条SQL,同时id与方法名相同

            b、parameterType定义传入的参数类型

            c、resultType 定义返回值类型

        自动映射

            想要完成自动必须满足如下要求

            ①开启自动映射

                开启映射可以在setting中设置autoMappingBehavior属性值,包含3个值:

                a、NONE,取消自动映射。

                b、PARTIAL,只会自动映射,没有定义嵌套结果集映射的结果集(默认)。

                c、FULL,会自动映射任何复杂的结果集(无论是否嵌套),这将会使性能下降

            ②SQL列名和JavaBean的属性一致,查询结果会自动回填到相应的字段中

                存在一个问题:往往SQL中列的命名是以下划线分隔,如:ROLE_NAME,而Java中采用驼峰的方式命名,如:roleName,这样就给一一对应关系造成麻烦。

                解决方法:

                a、在查询的时候使用列的别名的方式。

                    0602d409f228dfcb0fbdb507feac336fcf8.jpg

                b、在setting中设置mapUnderscoreToCameCase为true。

        传递多个参数的方法

        在我们用select语句查询数据的时候,大多时候都需要多个参数,上面我们了解了传递一个参数的方法,接下来我们介绍传递多个参数的方法,传递多个参数一共有3种方法。

        第一种:使用Map传递参数

            实现原理:就是将多个参数封装在Map集中,然后传给调用的接口,相应的parameterType的值也设置为map

            接口

public List<Role> findRolesByMap(Map<String, Object> parameterMap);

            Mapper.xml文件

<select id="findRolesByMap" parameterType="map"
		resultType="role">
		select id,role_name as roleName,note from t_role where
		role_name like
		concat('%',#{roleName},'%') and note like
		concat('%',#{note},'%')
</select>

            传递参数

 RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        Map<String, Object> parameterMap = new HashMap<>();
        parameterMap.put("roleName", "1");
        parameterMap.put("note", "1");
        List<Role> roles = roleMapper.findRolesByMap(parameterMap);

            弊端:因为Map需要键值对应,造成业务关联性不强,需要深入程序看代码,造成可读性降低。

        第二种:使用注解方式传递参数

            应用@Param注解来实现想要的功能

            接口:

public List<Role> findRolesByAnnotation(@Param("roleName") String roleName, @Param("note") String note);

            Mapper.xml文件

<select id="findRolesByAnnotation" resultType="role">
		select
		id,role_name as roleName,note from t_role where
		role_name like
		concat('%',#{roleName},'%') and note like
		concat('%',#{note},'%')
</select>

            问题:相对第一种方法,可读性大大提高,但是如果参数过多,会造成代码复杂,从而可读性仍然不高。

        第三种:使用JavaBean传递参数

            定义参数JavaBean

public class RoleParams {

    private String roleName;
    private String note;

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

            接口

public List<Role> findRolesByBean(RoleParams roleParam);

            Mapper.xml文件

<select id="findRolesByBean"
		parameterType="com.hys.mybatis.example3.pojo.RoleParams"
		resultType="role">
		select
		id,role_name as roleName,note from t_role where
		role_name like
		concat('%',#{roleName},'%') and note like
		concat('%',#{note},'%')
</select>

            调用

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        RoleParams roleParams = new RoleParams();
        roleParams.setRoleName("1");
        roleParams.setNote("1");
        List<Role> roles = roleMapper.findRolesByBean(roleParams);

        在开发过程中,要根据实际情况合理运用,第一种方法由于可读性太低不建议使用,当参数较少的时候使用第二种方法,当参数较多的时候使用第三种方法,还可以混合使用。
 

1.2 insert元素

        执行插入操作,应用时也需要相应的配置,具体配置方法如下

ab868f86d51ea6d3e5b5706b19b03c95ccc.jpg

        示例代码

<insert id="insertRole" parameterType="role">
		insert into
		t_role(role_name,note) values(#{roleName},#{note})
</insert>

        主键回填和自定义

        主键回填:主键有可能是自增字段,如MySQL中的auto_increment,在插入后我们往往需要获得主键,我们可以通过如下方法获得。

        JDBC中的Statement对象在执行插入的SQL后,可以通过getGeneratedKeys方法获得数据库生成的主键(需要数据库驱动支持),这样便能达到获取主键的功能。在insert语句中有一个开关属性useGeneratedKeys,用来控制是否打开这个功能,它的默为false。当打开了这个开关,还要配置其属性keyProperty或keyColumn,告诉系统把生成的主键放入哪个属性中,如果存在多个主键,就要用逗号(,)将它们隔开。

        示例:

<insert id="insertRole" parameterType="role"
		useGeneratedKeys="true" keyProperty="id">
		insert into
		t_role(role_name,note)
		values(#{roleName},#{note})
</insert>

        我们传入role对象无序设置id值,MyBatis用数据库设置进行处理,当插入完成后,它会回填JavaBean的id值。

        自定义主键 

        有时候主键可能依赖于某些规则,比如取消角色表(t_role)的id的递增规则,而将其规则修改为:

            当角色表记录为空时,id设置为1。当角色表记录不为空时,id设置为当前id加3。

            MyBatis对这样的场景也提供了支持,它主要依赖于selectKey元素进行支持,它允许自定义键值的生成规则。映射XML如下:

<insert id="insertRole" parameterType="role">
		<selectKey keyProperty="id" resultType="long" order="BEFORE">
			select
			if (max(id)=null,1,max(id)+3) from t_role
		</selectKey>
		insert into
		t_role(id,role_name,note)
		values(#{id},#{roleName},#{note})
</insert>

            上述代码定义了selectKey元素,它的keyProperty指定了采用哪个属性作为POJO的主键。resultType告诉MyBatis将返回一个long型的结果集,而order设置为BEFORE,说明它将于当前定义的SQL前执行。

1.3 update元素和delete元素

         因为update元素和delete元素比较简单,所以把它们放在一起论述。它们和insert的属性差不多,执行完也会返回一个整数,用以标识该SQL语句影响了数据库的记录行数。映射XML如下:

<update id="updateRole" parameterType="role">
		update t_role set
		role_name=#{roleName},note=#{note} where id=#{id}
</update>
<delete id="deleteRole" parameterType="long">
		delete from t_role where
		id=#{id}
</delete>

         最后MyBatis会返回一个整数,标识对应的SQL执行后会影响了多少条数据库表里的记录。至于参数可以参考select元素的参数规则,在MyBatis中它们的规则是通用的。

1.4 sql元素

        作用:当我们编写大量的重复sql语句的时候,为了提高代码可读性和降低重复率,我们可以把这些代码提取出来,放在sql元素中。这样在其他SQL语句中就可以引用这段代码。

        实例一(普通的sql代码块)

 <resultMap type="role" id="roleMap">
		<id property="id" column="id" />
		<result property="roleName" column="role_name" />
		<result property="note" column="note" />
</resultMap>

<sql id="roleCols">
	id,role_name,note
</sql>

<select id="getRole" parameterType="long" resultMap="roleMap">
	select
	<include refid="roleCols" />
	from t_role where id=#{id}
</select>

        实例二(可以传递参数的sql代码块)

<sql id="roleCols">
	${alias}.id,${alias}.role_name,${alias}.note
</sql>

<select id="getRole" parameterType="long" resultMap="roleMap">
	select
	<include refid="roleCols">
		<property name="alias" value="r" />
	</include>
	from t_role r where id=#{id}
</select>

        sql代码块中也可以引入其他的代码块,同样适用<include refid=""></include>

1.5 引入参数

        注:定义参数属性的时候,MyBatis不允许换行。

        参数配置

#{age,javaType=int ,jdbcType=NUMERIC}
#{age,javaType=int ,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
#{price,javaType=double,jdbcType=NUMERIC,numericScale=2}

        存储过程

        存储过程包含三种参数,输入参数(IN),输出参数(OUT),输入输出参数(INOUT),在MyBatis中通过指定mode属性来确定是哪一种参数,model属性有3个选项:IN,OUT,INOUT。当返回的是一个游标的时候(JdbcType=CURSOR),我们需要设置resultMap将存储过程的参数映射到对应的类型,MyBatis通过设置的resultMap自动为我们设置映射结果。其中javaType是可选的,不指定MyBatis会自动检测。

#{role,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=roleResultMap}

        传入的参数可能是结构体,需要指定语句类型的名称

        ccc66f9af84e8836f0b71ef2bf2f12deb54.jpg

        #{}和${}的区别

        #{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

        ${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

1.6 resultMap结果集映射

        前面我们了解了简单的结果映射resultType,但是要映射复杂的结果集的时候就需要用到resultMap,resultMap是用在mapper.xml文件中的

        resultMap的作用是定义映射规则、级联的更新、定制类型转化器等。resultMap定义的主要是一个结果集的映射关系,也就是SQL到Java Bean的映射关系定义,它也支持级联等特性。只是MyBatis现有的版本只支持resultMap查询,不支持更新或者保存,更不必说级联的更新、删除和修改了。

        基本框架

<resultMap>
	<constructor>
		<idArg />
		<arg />
	</constructor>
	<id />
	<result />
	<association />
	<collection />
	<discriminator>
		<case />
	</discriminator>
</resultMap>

            resultMap结果映射集的配置项如上所示,接下来我们将进行一一介绍

        constructor元素

        作用:用于配置构造方法,当一个POJO不存在没有参数的构造器时,就要用它来配置

        代码:

<resultMap ...>
	<constructor>
		<idArg column="id" javaType="int" />
		<arg column="role_name" javaType="string" />
	</constructor>
	<!-- do something... -->
</resultMap>

        代码中idArg定义了主键是哪个属性,arg定义普通的属性。

        id和result元素

        作用:id表示哪个列是主键,允许有多个主键,多个主键称为联合主键,result是配置POJO到SQL列名的映射关系

        id和result元素共有的属性:

        f4cfa4112e53e26794c02c2bbc661ded649.jpg

        结果集存储方法

        第一种:使用map存储结果集

            a、通过@MapKey指定Map的key,这个key一般为SQL的一个列名通常为主键

@MapKey("id")
Map<Long, UserInfo> getUserInfoMap();

@MapKey("id")
Map<Long, Map<String,Object>> getUserValueMap();

            b、映射文件

<select id="getUserValueMap" resultType="map" >
        select id,username,sex from user_info
        from user_info
</select>
<resultMap id="UserResultMap" type="com.xixicat.domain.UserInfo">
        <result property="id" column="id" />
        <result property="username" column="username" />
        <result property="sex" column="sex" />
    </resultMap>
<select id="getUserInfoMap" resultMap="UserResultMap">
   select id,username,sex from user_info
</select>

                 注:第一段配置文件的结果,map的value也是一个map,value中是key为列名,value为具体数据的map。第二段配置文件的结果是java对象。上面两种方法都可以得到多条查询结果,但是,第一种得到的map中value是map,第二种得到的map中value是java对象。

            map原则上可以匹配所有结果集,但用map存储结果集的缺点是造成可读性下降。

        第二种:使用POJO存储结果集

            步骤如下:

            a、映射文件中配置resultMap

<resultMap id="UserResultMap" type="com.xixicat.domain.UserInfo">
        <result property="id" column="id" />
        <result property="username" column="username" />
        <result property="sex" column="sex" />
</resultMap>

            b、映射文件中配置相应的操作

<select id="getUserInfoMap" resultMap="UserResultMap">
   select id,username,sex from user_info
</select>

        在配置中还有三个元素,association,colleciation,discriminatior这三个元素分别代表一对一级联、多对多级联、鉴别器级联,由于级联知识点相对复杂,我们在下一节单独介绍。

1.7 级联

        通俗的讲级联就是一个POJO里包含一个或者一组POJO,比如一个学生有一个学生证(一对一级联),我们可以在学生信息类中添加一个学生证类对象属性,在查询的时候要根据学生ID查询学生证信息,还有一种情况是学生和选课的关系,一个学生可以选多门课程(一对多级联)。 在MyBatis中还有一种被称为鉴别器的级联,它是一种可以选择具体实现类的级联,比如要查找雇员及其体检表的信息,但是雇员有性别之分,而根据性别的不同,其体检表的项目也会不一样,比如男性体检表可能有前列腺的项目,而女性体检表可能有子宫的项目,那么体检表就应该分为男性和女性两种,从而根据雇员性别区分关联。

         级联不是必须的,级联的好处是获取关联数据十分便捷,但是级联过多会增加系统的复杂度(比如查询学生信息,会同时加载学生证信息等,多一层级联就多一条SQL语句),同时降低系统的性能,此增彼减,所以当级联的层级超过3层时,就不要考虑使用级联了,因为这样会造成多个对象的关联,导致系统的耦合、复杂和难以维护。在现实的使用过程中,要根据实际情况判断是否需要使用级联。

          级联的分类

            a、一对一级联

            b、一对多级联

            c、鉴别器级联

            由于多对多级联相对复杂,而且可以被一对多级联替换,所以MyBatis没有多对多级联

        接下来我们将介绍三种级联的代码实现,我们以学生信息为例,一个学生有一张学生证,一个学生可以选多门课程,一门课程对应一张成绩单,根据性别不同拥有不同的体检报告。

        0d48d07bd8ca81b0681bcf778ca8b9dac55.jpg

       配置文件

<mapper namespace="com.learn.chapter4.mapper.StudentMapper">
	<resultMap id="studentMap" type="com.learn.chapter4.po.StudentBean">
		<id property="id" column="id"/>
		<result propert="cnname" column="cnname"/>
		<result propert="sex" column="sex" jdbcType="INTEGER" javaType="com.learn.chapter4.enums.SexEnum"
			typeHandler="com.learn.chapter4.typehandler.SexTypeHandler"/>
		<result propert="note" column="note"/>
		<association property="studentSelfcard" column="id"
			select="com.learn.chapter4.mapper.StudentSelfcardMapper.findStudentsSelfcardByStudentId"/>
		<collection property="studentLectureList" column="id"
			select="com.learn.chapter4.mapper.StudentLectureMapper.findStudentLectureByStuId"/>
		<discriminator javaType="int" column="sex">
			<case value="1" resultMap="maleStudentMap"/>
			<case value="2" resultMap="femaleStudentMap"/>
		</discriminator>
	</resultMap>
	<select id="getStudent" parameterType="int" resultMap="studentMap">
		select id,cnname,sex,note from t_student where id = #{id}
	</select>
	<resultMap id="maleStudentMap" type="com.learn.chapter4.po.MaleStudentBean" extends="studentMap">
		<collection property="studentHealthMaleList" select="com.learn.
			chapter4.mapper.StudentHealthMaleMapper.findStudentHealthMaleByStuId" columns="id"/>
	</resultMap>
	<resultMap id="femaleStudentMap" type="com.learn.chapter4.po.FemaleStudentBean" extends="studentMap">
		<collection property="studentHealthMaleList" select="com.learn.
			chapter4.mapper.StudentHealthMaleMapper.findStudentHealthFemaleByStuId" columns="id"/>
	</resultMap>
</mapper>
		

          文件解析:

           association:select指的是要使用哪个具体的SQL去查询,点位方式是mapper文件的命名空间+SQL的ID,这样便可以指向对应Mapper的SQL,MyBatis就会通过对应的SQL将数据查询回来。column代表SQL的列,用作参数传递给select属性制定的SQL,如果是多个参数,则需要用逗号隔开。

            collection:property属性指定将结果集存储在哪里,select指定的查询将返回的结果集存入。

            discriminator:column属性指定用哪个字段进行鉴别,case相当于switch语句中的case,根据sex的值选择相应的resultMap。

            extends属性指定当前resultMap继承哪个resultMap,这个继承和类的继承一样,可以加入新的配置。

        注:这个文件中只写了核心的级联配置,级联操作所涉及的配置文件并没有给出(如:StudentSelfcardMapper.xml),这些文件是最基础的映射文件,在这里不进行一一给出。

        级联带来的问题

            a、当级联层数过多的时候,造成复杂度过高,不利于维护,同时级联往往会获取所有的数据,这样会增加执行的SQL语句,导致性能降低。

            b、N+1问题,有N个关联,比如Student表关联了很多表,当我们取出一个student对象,所有的信息都会被取出,导致性能下降。

            为了解决这样的问题,MyBatis给出了延迟加载功能。

        延迟加载

            当我们读取数据的时候,只读取当前需要的信息,级联的信息只有当我们需要的时候才读取。

            配置级联的两个全局参数lazyLoadingEnabled和aggressiveLazyLoading,这两个参数是在setting中设置。

            lazyLoadingEnabled:全局开启或关闭延迟加载

            aggressiveLazyLoading:是否开启层级加载

            d233a67a54fdf693b83cf7652ee81702bec.jpg

            层级加载:级联有可能延伸多层,如果开启层级加载,当加载某一个层级中的一个对象的时候,这个层级中所有的对象都会被加载;如果关闭层级加载,访问那个对象就加载那个对象,不会加载别的对象。

            问题:如果使用严格的延迟加载,按需求加载对象,同样会带来一个问题。当我们需要某个对象立即加载的时候这种设置就非常的不灵活。如在加载学生信息的同时立即加载学生成绩信息。

            解决方法:MyBatis提供了fetchType属性,我们可以在局部设置延迟加载,弥补上面的不灵活。 fetchType可加在在级联元素(association、collection,注意,discriminator没有这个属性可配置)中 ,覆盖全局设置。

            fechType属性

                有两个属性值:

                a、eager,立即加载

                b、lazy,延迟加载        

                实例:

<association property="studentSelfcard" column="id" fetchType="lazy"
			select="com.learn.chapter4.mapper.StudentSelfcardMapper.findStudentsSelfcardByStudentId"/>
<collection property="studentLectureList" column="id" fetchType="eager"
			select="com.learn.chapter4.mapper.StudentLectureMapper.findStudentLectureByStuId"/>

        还存在另外一种级联,用一条SQL查询所有信息,这种SQL语句复杂,需要根据实际情况选择。这种方式没有N+1的问题。

        16563cb99114ef5b479e20e5e5e7c130c1e.jpg

        ofType:定义collection里面的泛型是什么Java类型。

1.8缓存

        将常用的数据存放在内存中,提高整个系统的性能。缓存的关键是存储内容访问的命中率。缓存分为系统缓存和自定义缓存,系统缓存分为一级缓存和二级缓存,自定义缓存可以应用目前主流的缓存服务器,如:MongonDB、Redis、Ehcache等。

        系统缓存

            一级缓存(默认开启)

            作用域:只相对于同一个SqlSession而言。

            二级缓存

            作用域:在SqlSessionFactory层面上能够提供给各个SqlSession对象共享。

            开启条件:返回的POJO必须是可序列化的,即要求实现Serializable接口,否则会抛出异常。

            开启方法:<cache/>

            默认配置

                a、映射语句文件中的所有select语句将会被缓存。

                b、映射语句中的所有insert、update、delete语句会刷新缓存。

                c、缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来回收。

                d、根据时间表,比如No Flush Interval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新。

                e、缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用。

                f、缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

            更改配置

                4013c8f70e8bd937fd637f185e65c3b55f6.jpg

                属性

                ①eviction:缓存回收策略

                    a、LRU:最近最少使用的

                    b、FIFO:先进先出

                    c、SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象

                    d、WEAK:弱引用,更积极的移除基于垃圾回收器转台和弱引用规则的对象。

                ②flushInterval:刷新间隔时间,单位为毫秒。

                ③size:引用数目,一个正整数,代表缓最对可以存储多少个对象,不宜过大。

                ④readOnly:只读,意味着缓存数据只能读取而不能修改,默认是false。

        自定义缓存

            我们可以自己定制缓存。如实现Redis缓存。需要实现org.apache.ibatis.cache.Cache

            用如下代码配置

<cache type="缓存实现类的全名称">
    <property name="host" value="localhost"/>
</cache>

            配置SQL层面上的缓存规则:userCache和flushCache分别是是否使用缓存和插入后是否刷新缓存。

            fbb9c7e40728226f3d3c9d9565d318bd05f.jpg

总结

       我们已经完整的介绍了映射器的配置,其中的重点是resultMap的配置,级联的使用,和缓存的配置,通过学习我们了解了MyBatis强大的功能。

转载于:https://my.oschina.net/u/3942106/blog/1937020

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值