MyBatis开发要点


MyBatis源码学习系列文章目录



前言

本系列教程不是初级教程,需要对MyBatis有一定的使用基础、MyBatis.版本为3.4.6
MyBatis官方网站:https://mybatis.org/mybatis-3/zh/index.html

在maven中依赖为

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.4.6</version>
</dependency>

提示:以下是本篇文章正文内容,下面案例可供参考

一、为什么需要ORM框架

首先我们得看一下传统JDBC编程的代码

static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true";

// Database credentials
static final String USER = "root";
static final String PASS = "lixun033";

@Test
public void QueryPreparedStatementDemo() {
	Connection conn = null;
	PreparedStatement stmt = null;
	List<TUser> users = new ArrayList<>();
	try {
		// STEP 2: 注册mysql的驱动
		Class.forName("com.mysql.jdbc.Driver");

		// STEP 3: 获得一个连接
		conn = DriverManager.getConnection(DB_URL, USER, PASS);

		// STEP 4: 创建一个查询
		String sql;
		sql = "SELECT * FROM t_user where userName= ? ";
		stmt = conn.prepareStatement(sql);
		stmt.setString(1, "lison");
		
		ResultSet rs = stmt.executeQuery();
		
		// STEP 5: 从resultSet中获取数据并转化成bean
		while (rs.next()) {
			System.out.println("------------------------------");
			// Retrieve by column name
			TUser user = new TUser();
			user.setId(rs.getInt("id"));
			user.setUserName(rs.getString("userName"));
			user.setRealName(rs.getString("realName"));
			user.setSex(rs.getByte("sex"));
			user.setMobile(rs.getString("mobile"));
			user.setEmail(rs.getString("email"));
			user.setNote(rs.getString("note"));	
			users.add(user);
		}
		// STEP 6: 关闭连接
		rs.close();
		stmt.close();
		conn.close();
	} catch (SQLException se) {
		// Handle errors for JDBC
		se.printStackTrace();
	} catch (Exception e) {
		// Handle errors for Class.forName
		e.printStackTrace();
	} finally {
		// finally block used to close resources
		try {
			if (stmt != null)
				stmt.close();
		} catch (SQLException se2) {
		}// nothing we can do
		try {
			if (conn != null)
				conn.close();
		} catch (SQLException se) {
			se.printStackTrace();
		}
	}
	System.out.println("there are "+users.size()+" users in the list!");
}

不难看出传统的JDBC编程存在的弊端

  1. 工作量大,操作数据库至少需要5步
  2. 业务代码和技术代码耦合
  3. 连接资源手动关闭,带来了隐患

二、MyBatis快速入门

MyBatis的前身是iBatis,起源于“Internet”和“ibatis”的组合,本质是一种半自动的ORM框架,除了POJO和映射关系之外,还需要编写SQL语句;MyBatis映射文件三要素:SQL、映射规则和POJO.

1. 加入MyBatis的映射文件

TUserMapper.xml作为映射文件的命名空间namespace与接口对应,比如如下的映射文件对应的接口为

public interface TUserMapper {
	
	TUser selectByPrimaryKey(Integer id);
}	

在这个文件当中,用户编写查询的语句,返回结果类型通过resultType指定。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.enjoylearning.mybatis.mapper.TUserMapper">

	<select id="selectByPrimaryKey" resultType="com.enjoylearning.mybatis.entity.TUser" >
		select
		id, userName, realName, sex, mobile, email, note
		from t_user
		where id = #{id,jdbcType=INTEGER}
	</select>
	
</mapper>	

2. 加入MyBatis的核心配置文件

在核心配置文件mybatis-config.xml中引入数据连接池、系统配置参数、别名以及上面的映射文件。

<?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="db.properties"/>
	
 	<settings>
	
		<!-- 设置自动驼峰转换		 -->
		<setting name="mapUnderscoreToCamelCase" value="true" />

		<!-- 开启懒加载 -->		
		 <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。默认:true -->
	  <setting name="aggressiveLazyLoading" value="false" />
	 
	</settings>
	
	<!--配置environment环境 -->
	<environments default="development">
		<!-- 环境配置1,每个SqlSessionFactory对应一个环境 -->
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc_driver}" />
				<property name="url" value="${jdbc_url}" />
				<property name="username" value="${jdbc_username}" />
				<property name="password" value="${jdbc_password}" />
			</dataSource>
		</environment>


	</environments>

	<!-- 映射文件,mapper的配置文件 -->
	<mappers>
		<!--直接映射到相应的mapper文件 -->
		<mapper resource="sqlmapper/TUserMapper.xml"/>
	</mappers>

</configuration>  

在properties节点当中定义引入的配置文件

jdbc_driver=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
jdbc_username=root
jdbc_password=lixun033

代码如下(示例):

3. 对应代码

	private SqlSessionFactory sqlSessionFactory;
	
	@Before
	public void init() throws IOException {
		//--------------------第一阶段---------------------------
	    // 1.读取mybatis配置文件创SqlSessionFactory
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		// 1.读取mybatis配置文件创SqlSessionFactory
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		inputStream.close();
	}
	
	@Test
	// 快速入门
	public void quickStart() throws IOException {
		//--------------------第二阶段---------------------------
		// 2.获取sqlSession	
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 3.获取对应mapper
		TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
		
		//--------------------第三阶段---------------------------
		// 4.执行查询语句并返回单条数据
		TUser user = mapper.selectByPrimaryKey(2);
		System.out.println(user);
	}

核心类分析:

  1. SqlSessionFactoryBuilder:读取配置信息创建SqlSessionFactory,建造者模式,方法级别声明周期
  2. SqlSessionFactory:创建SqlSession,工厂单例模式,存在于程序的整个生命周期
  3. SqlSession:代表一次数据库连接,一般通过调用Mapper访问数据库,也可以直接发送SQL执行;线程不安全,要保证线程独享(方法级别)
  4. SQL Mapper:由一个Java接口和XML文件组成,包含了要执行的SQL语句和结果集映射规则。方法级别生命周期。

三、MyBatis开发要点

1. resultType还是resultMap

resultType

当使用resultType做SQL语句返回结果类型处理时,对于SQL语句查询出的字段在相应的POJO中必须有和它相同的字段对应,而resultType中的内容就是pojo在本项目中的定义。
自动映射注意事项:
1、 前提:SQL列名与JavaBean的属性是一致的
2、 使用resultType,如果简写需要配置参数类型别名typeAliases
比如在上面的案例当中,如果mapper文件中改为如下方式

<select id="selectByPrimaryKey" resultType="TUser" >
	select
	id, userName, realName, sex, mobile, email, note
	from t_user
	where id = #{id,jdbcType=INTEGER}
</select>

此时必须在配置文件mybatis-config.xml当中添加typeAliases

<!-- 别名定义 -->
<typeAliases>
	<package name="com.enjoylearning.mybatis.entity" />
</typeAliases>

3、 如果列名和JavaBean不一致,但列名符合单词下划线分割原则,Java是驼峰命名法,
比如在以下的java类和数据库列名中,userName与user_name、realName与real_name映射。

public class TUser implements Serializable{
	
    private Integer id;

    private String userName;

    private String realName;

    private Byte sex;

    private String mobile;

    private String email;

    private String note;
}    
<select id="selectAll" resultType="TUser">
	select
	id, user_name, real_name, sex, mobile, email, note
	from t_user_test
</select>

则通过核心配置文件中settings设置mapUnderscoreToCamelCase解决问题

    <settings>
        <!-- 设置自动驼峰转换		 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

resultMap

resultMap元素是MyBaits当中最重要的元素,它可以让你从90%的JDBC ResultSets数据中提取代码中解放出来,在对复杂语句进行联合映射的时候,它可以代替数千行明确的结果映射,而复杂一点的语句只需要描述它们的关系就行了。

属性描述
id当前命名空间中的一个唯一标识
type类的完全限定名或者一个类型别名
autoMapping如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射,这个属性会覆盖全局的属性autoMappingBehavior.默认值为unset

使用场景总结:
1、 字段有自定义的转化规则
2、 复杂的多表查询

比如以下为复杂的多表查询

<resultMap id="UserResultMap" type="com.enjoylearning.mybatis.entity.TUser" autoMapping="true">
	<id column="id" property="id" />
       <result column="userName" property="userName"/>
	<result column="realName" property="realName" />
	<result column="sex" property="sex" />
	<result column="mobile" property="mobile" />
	<result column="email" property="email" />
	<result column="note" property="note" />
	<association property="position" javaType="TPosition" columnPrefix="post_">
		<id column="id" property="id"/>
		<result column="name" property="postName"/>
		<result column="note" property="note"/>
	</association>
</resultMap>

<select  id="selectTestResultMap" resultMap="UserResultMap" >
	select
	    a.id, 
	    userName,
		realName,
		sex,
		mobile,
		email,
		a.note,
		b.id  post_id,
		b.post_name,
		b.note post_note
	from t_user a,
		t_position b
	where a.position_id = b.id

</select>

到底应该用resultType还是resultMap

强制使用resultMap,不要用resultType当返回参数(简单数据类型除外),即使所有类型属性名与数据库字段一一对应,也需要定义。

参考阿里开发手册
在这里插入图片描述

2. 怎么传递多个参数

有三种方式
1、使用map的方式

List<TUser> selectByEmailAndSex1(Map<String, Object> param);
<select id="selectByEmailAndSex1" resultMap="BaseResultMap"		parameterType="map">
	select
	<include refid="Base_Column_List" />
	from t_user a
	 where a.email like CONCAT('%', #{email}, '%') and
	 a.sex =#{sex} 
</select>

2、使用注解的方式

List<TUser> selectByEmailAndSex2(@Param("email")String email,@Param("sex")Byte sex);
<select id="selectByEmailAndSex2" resultMap="BaseResultMap">
	select
	<include refid="Base_Column_List" />
	from t_user a
	where a.email like CONCAT('%', #{email}, '%') and
	a.sex =	#{sex}
</select>

3、使用JavaBean的方式

package com.enjoylearning.mybatis.entity;

public class EmailSexBean {

    private Byte sex;

    private String email;
	
	// getter setter略	
}

List<TUser> selectByEmailAndSex3(EmailSexBean esb);
<select id="selectByEmailAndSex3" resultMap="BaseResultMap"
	parameterType="com.enjoylearning.mybatis.entity.EmailSexBean">
	select
	<include refid="Base_Column_List" />
	from t_user a
	where a.email like CONCAT('%', #{email}, '%') and
	a.sex =	#{sex}
</select>
方式描述
使用map传递参数可读性差,导致可维护性和可扩展性差,杜绝使用
使用注解传递参数直观明了,当参数小于5的时候,建议使用
使用JavaBean传递参数当参数个数大于5的时候,建议使用

在这里插入图片描述

3. 怎么获取主键

1、通过insert/update标签相关属性

属性描述
useGeneratedKeys仅对insert和update有用,这会令MyBatis使用JDBC的java.sql.Statement#getGeneratedKeys方法来取出由数据库内部生成的主键(比如像MySQL和SQL Server这样的关系型数据库管理系统的自动递增字段),默认为false
keyProperty(仅对insert和update有用)唯一标识一个属性,MyBatis会通过getGeneratedKeys的返回值或者通过insert语句的selectKey子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表

注意:自增长序号不是简单的行数+1,而是序号最大值+1

<insert id="insert1" parameterType="com.enjoylearning.mybatis.entity.TUser" useGeneratedKeys="true"
		keyProperty="id">
	insert into t_user (id, userName, realName,
						sex, mobile,
						email,
						note, position_id)
	values (#{id,jdbcType=INTEGER},
			#{userName,jdbcType=VARCHAR},
			#{realName,jdbcType=VARCHAR},
			#{sex,jdbcType=TINYINT}, #{mobile,jdbcType=VARCHAR},
			#{email,jdbcType=VARCHAR},
			#{note,jdbcType=VARCHAR},
			#{position.id,jdbcType=INTEGER})
</insert>

2、通过selectKey元素

属性描述
keyPropertyselectKey语句结果应该被设置的目标属性,如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表
order结果的类型。MyBatis通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis允许任何简单类型作为主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的Object或者一个Map
resultType这可以被设置为BEFORE或AFTER。如果设置为BEFORE,那么它会首先选择主键,设置keyProperty然后执行插入语句。如果设置为AFTER,那么先执行插入语句,然后获取主键字段;mysql数据库自增长的方式order设置为AFTER,oracle数据库通过sequence获取主键order设置为BEFORE
<insert id="insert2" parameterType="com.enjoylearning.mybatis.entity.TUser">

	<selectKey keyProperty="id" order="AFTER" resultType="int">
		select
		LAST_INSERT_ID()
	</selectKey>
	insert into t_user (id, userName, realName,
	sex, mobile,
	email,
	note,
	position_id)
	values (#{id,jdbcType=INTEGER},
	#{userName,jdbcType=VARCHAR},
	#{realName,jdbcType=VARCHAR},
	#{sex,jdbcType=TINYINT}, #{mobile,jdbcType=VARCHAR},
	#{email,jdbcType=VARCHAR},
	#{note,jdbcType=VARCHAR},
	#{position.id,jdbcType=INTEGER})
</insert>

如果是Oracle数据库则是

<selectKey keyProperty="id" order="BEFORE" resultType="int">
	select SEQ_ID.nextval from dual
</selectKey>

4. SQL元素和SQL的参数

SQL元素:用于定义可重用的SQL代码段,可以包含在其他语句中
SQL参数:向SQL语句中传递的可变参数,可分为预编译#{}和传值${}两种

  • 预编译#{}:将传入的数据都当成一个字符串,会对自动传入的数据加一个单引号,能够很大程度上防止sql注入
  • 传值${}:传入的数据直接显示在生成的sql中,无法防止sql注入;使用场景:动态报表,表名、选取的列是动态的,order by和in操作,可以考虑使用$
List<TUser> selectBySymbol(@Param("tableName")String tableName,
                           @Param("inCol")String inCol,
                           @Param("orderStr")String orderStr,
                           @Param("userName")String userName);
<select id="selectBySymbol" resultMap="BaseResultMap">
    select ${inCol}
    from ${tableName} a
    where a.userName = #{userName}
    order by ${orderStr}
</select>

使用方式

@Test
// 参数#和参数$区别测试(动态sql 入门)
public void testSymbol() {
	// 2.获取sqlSession
	SqlSession sqlSession = sqlSessionFactory.openSession();
	// 3.获取对应mapper
	TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
	
	String inCol = "id, userName, realName, sex, mobile, email, note";
	String tableName = "t_user";
	String userName = "'xxx' or 1=1";
	String orderStr = "sex,userName";
	
	List<TUser> list = mapper.selectBySymbol(tableName, inCol, orderStr, userName);
	System.out.println(list.size());
	
}

在这里插入图片描述

5. 动态SQL

元素作用备注
if判断语句单条件分支判断
choose、when、otherwise相当于case when多条件分支判断
trim、where、set辅助元素用于sql拼装问题
foreach循环语句在in语句等列举条件常用,常用语实现批量操作
  • 在select中使用if元素,在查询条件之前加where关键字,可以去掉语句的第一个and或者or

以下为采用1=1的方式解决if存在为空导致查询语句多出where字段的情况

List<TUser> selectIfOper(@Param("email")String email,@Param("sex")Byte sex);
<select id="selectIfOper" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from t_user a
    where 1=1
    <if test="email != null and email != ''">
        and a.email like CONCAT('%', #{email}, '%')
    </if>
    <if test="sex != null ">
        and a.sex = #{sex}
    </if>
</select>

使用choose…when…也可以

<select id="selectChooseOper" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from t_user a
    <where>
        <choose>
            <when test="email != null and email != ''">
                and a.email like CONCAT('%', #{email}, '%')
            </when>
            <when test="sex != null">
                and a.sex = #{sex}
            </when>
            <otherwise>
                and 1=1
            </otherwise>
        </choose>
    </where>
</select>

但使用where标签最优雅

<select id="selectIfandWhereOper" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from t_user a
    <where>
        <if test="email != null and email != ''">
            and a.email like CONCAT('%', #{email}, '%')
        </if>
        <if test="sex != null ">
            and a.sex = #{sex}
        </if>
    </where>
</select>
  • 在update中使用if元素,set元素可以在值设置之前加set关键字,同时去掉语句最后一个逗号

以下为通常模式,但是是建立在最后一个字段note不为空的情况下的,否则因为之前字段后面的逗号导致更新语句错误

<update id="updateIfOper" parameterType="com.enjoylearning.mybatis.entity.TUser">
    update t_user
    set
    <if test="userName != null">
        userName = #{userName,jdbcType=VARCHAR},
    </if>
    <if test="realName != null">
        realName = #{realName,jdbcType=VARCHAR},
    </if>
    <if test="sex != null">
        sex = #{sex,jdbcType=TINYINT},
    </if>
    <if test="mobile != null">
        mobile = #{mobile,jdbcType=VARCHAR},
    </if>
    <if test="email != null">
        email = #{email,jdbcType=VARCHAR},
    </if>
    <if test="note != null">
        note = #{note,jdbcType=VARCHAR}
    </if>
    where id = #{id,jdbcType=INTEGER}
</update>

通过MyBatis提供的set标签优雅解决

<update id="updateIfAndSetOper" parameterType="com.enjoylearning.mybatis.entity.TUser">

    update t_user
    <set>
        <if test="userName != null">
            userName = #{userName,jdbcType=VARCHAR},
        </if>
        <if test="realName != null">
            realName = #{realName,jdbcType=VARCHAR},
        </if>
        <if test="sex != null">
            sex = #{sex,jdbcType=TINYINT},
        </if>
        <if test="mobile != null">
            mobile = #{mobile,jdbcType=VARCHAR},
        </if>
        <if test="email != null">
            email = #{email,jdbcType=VARCHAR},
        </if>
        <if test="note != null">
            note = #{note,jdbcType=VARCHAR},
        </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
</update>
  • 在insert中使用if元素,trim元素可以帮助拼接columns和values
    <insert id="insertIfOper" parameterType="com.enjoylearning.mybatis.entity.TUser">
        insert into t_user (
        <if test="id != null">
            id,
        </if>
        <if test="userName != null">
            userName,
        </if>
        <if test="realName != null">
            realName,
        </if>
        <if test="sex != null">
            sex,
        </if>
        <if test="mobile != null">
            mobile,
        </if>
        <if test="email != null">
            email,
        </if>
        <if test="note != null">
            note
        </if>
        )
        values(
        <if test="id != null">
            #{id,jdbcType=INTEGER},
        </if>
        <if test="userName != null">
            #{userName,jdbcType=VARCHAR},
        </if>
        <if test="realName != null">
            #{realName,jdbcType=VARCHAR},
        </if>
        <if test="sex != null">
            #{sex,jdbcType=TINYINT},
        </if>
        <if test="mobile != null">
            #{mobile,jdbcType=VARCHAR},
        </if>
        <if test="email != null">
            #{email,jdbcType=VARCHAR},
        </if>
        <if test="note != null">
            #{note,jdbcType=VARCHAR}
        </if>
        )
    </insert>
  • 使用foreach拼接in条件
List<TUser> selectForeach4In(String[] names);

int insertForeach4Batch(List<TUser> users);
<select id="selectForeach4In" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from t_user a
    where a.userName in
    <foreach collection="array" open="(" close=")" item="userName" separator=",">
        #{userName}
    </foreach>
</select>


<insert id="insertForeach4Batch" useGeneratedKeys="true" keyProperty="id">
    insert into t_user (userName, realName,
    sex, mobile,email,note,
    position_id)
    values
    <foreach collection="list" separator="," item="user">
        (
        #{user.userName,jdbcType=VARCHAR},
        #{user.realName,jdbcType=VARCHAR},
        #{user.sex,jdbcType=TINYINT},
        #{user.mobile,jdbcType=VARCHAR},
        #{user.email,jdbcType=VARCHAR},
        #{user.note,jdbcType=VARCHAR},
        #{user.position.id,jdbcType=INTEGER}
        )
    </foreach>

6. 使用MyBatis怎么进行批量的操作

  • 通过foreach动态拼接SQL语句,上面已经见过了
  • 使用BATCH类型的excutor

对于原生java采用的方式如下

@Test
public void updateDemo() {
    Connection conn = null;
    Statement stmt = null;
    try {
        // STEP 2: 注册mysql的驱动
        Class.forName("com.mysql.jdbc.Driver");

        // STEP 3: 获得一个连接
        System.out.println("Connecting to database...");
        conn = DriverManager.getConnection(DB_URL, USER, PASS);

        // STEP 4: 关闭自动提交
        conn.setAutoCommit(false);

        stmt = conn.createStatement();


        // STEP 5: 创建一个更新
        System.out.println("Creating statement...");
        String sql1 = "update t_user  set mobile= '13125858455' where userName= 'lison' ";
        String sql2 = "insert into t_user ( userName) values ('deer')";
        stmt.addBatch(sql1);
        stmt.addBatch(sql2);
        System.out.println(stmt.toString());//打印sql
        int[] executeBatch = stmt.executeBatch();
        System.out.println("此次修改影响数据库的行数为:" + Arrays.toString(executeBatch));

        // STEP 6: 手动提交数据
        conn.commit();

        // STEP 7: 关闭连接
        stmt.close();
        conn.close();
    } catch (SQLException se) {
        // Handle errors for JDBC
        try {
            conn.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        se.printStackTrace();
    } catch (Exception e) {
        try {
            conn.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        e.printStackTrace();
    } finally {
        // finally block used to close resources
        try {
            if (stmt != null)
                stmt.close();
        } catch (SQLException se2) {
        }// nothing we can do
        try {
            if (conn != null)
                conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        }
    }
}

在MyBatis中如下

@Test
// 批量更新
public void testBatchExcutor() {
	// 2.获取sqlSession
	SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, true);
	// 3.获取对应mapper
	TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
	
	TUser user = new TUser();
	user.setUserName("mark");
	user.setRealName("毛毛");
	user.setEmail("xxoo@163.com");
	user.setMobile("18695988747");
	user.setNote("mark's note");
	user.setSex((byte) 1);
	TPosition positon1 = new TPosition();
	positon1.setId(1);
	user.setPosition(positon1);
	System.out.println(mapper.insertSelective(user));
	
	TUser user1 = new TUser();
	user1.setId(3);
	user1.setUserName("cindy");
	user1.setRealName("王美丽");
	user1.setEmail("xxoo@163.com");
	user1.setMobile("18695988747");
	user1.setNote("cindy's note");
	user1.setSex((byte) 2);
	user.setPosition(positon1);
	System.out.println(mapper.updateIfAndSetOper(user1));
	
	sqlSession.commit();
	System.out.println("----------------");
	System.out.println(user.getId());
	System.out.println(user1.getId());
}

7. 关联查询

关联查询几个需要注意的细节

  1. 超过三个表禁止join,需要join的字段,数据类型必须绝对一致,多表关联查询时,保证被关联的字段有索引
    在这里插入图片描述
  2. 不得使用外键与级联,一切外键概念必须在应用层中解决
    在这里插入图片描述
  3. 字段允许适当冗余,以提高查询性能,但必须考虑数据一致
    在这里插入图片描述

为什么超过三个表禁止join?
大部分数据库的性能太弱了,尤其是设计到大数据量的多表join的查询,需要的对比与运算的量是会急速增长的,而数据库优化器在多表场合可能不是执行最优的计划,所以这条规范限制了join表的个数,还提交了join字段类型必须一致并有索引,那有这种约束复杂SQL怎么实现?考虑如下三中方式减少join表的关联

  • 以上的第三点:适当冗余字段
  • 分两次select,第一次select取得主表数据,第二次查从表数据
  • 将热点数据存缓存,提高数据的读取效率

关联元素:association用于表示一对一关系,collection用于表示一对多关系

  • 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
  • 嵌套查询:通过执行另一个SQL映射语句来返回预期的复杂类型

一对一关联嵌套结果方式

association标签 嵌套结果方式 常用属性:

属性说明
property对应实体类的属性名,必填项
javaType属性对应的Java类型
resultMap可以直接使用现有的resultMap,而不需要在这里配置映射关系
columnPrefix查询列的前缀,配置前缀后,在子标签配置result的column时可以省略前缀部分
<resultMap id="userAndPosition1" extends="BaseResultMap" type="com.enjoylearning.mybatis.entity.TUser">
    <association property="position" javaType="TPosition" columnPrefix="post_">
        <id column="id" property="id"/>
        <result column="name" property="postName"/>
        <result column="note" property="note"/>
    </association>
</resultMap>

<select id="selectUserPosition1" resultMap="userAndPosition1">
    select a.id,
           userName,
           realName,
           sex,
           mobile,
           email,
           a.note,
           b.id   post_id,
           b.post_name,
           b.note post_note
    from t_user a,
         t_position b
    where a.position_id = b.id

</select>

开发小技巧:
1. resultMap可以通过extends实现继承关系,简化很多配置工作量
2. 关联的表查询查询的类添加前缀是编程的好习惯
3. 通过添加完整的命名空间,可以引用其他xml文件的resultMap。

一对一关联嵌套查询方式

association标签 嵌套查询方式 常用属性

属性说明
select另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套结果的值
column列名(或别名),将主查询中列的结果作为嵌套查询的参数
fetchType数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载,这个配置会覆盖全局的lazyLoadingEnabled配置
<resultMap id="BaseResultMap" type="com.enjoylearning.mybatis.entity.TUser">

    <!-- <constructor> <idArg column="id" javaType="int"/> <arg column="userName"
        javaType="String"/> </constructor> -->
    <id column="id" property="id"/>
    <result column="userName" property="userName"/>
    <result column="realName" property="realName"/>
    <result column="sex" property="sex"/>
    <result column="mobile" property="mobile"/>
    <result column="email" property="email"/>
    <result column="note" property="note"/>
</resultMap>

<resultMap id="userAndPosition2" extends="BaseResultMap" type="com.enjoylearning.mybatis.entity.TUser">
    <association property="position" fetchType="lazy" column="position_id"
                 select="com.enjoylearning.mybatis.mapper.TPositionMapper.selectByPrimaryKey"/>
</resultMap>

<select id="selectUserPosition2" resultMap="userAndPosition2">
    select a.id,
           a.userName,
           a.realName,
           a.sex,
           a.mobile,
           a.position_id
    from t_user a
</select>

对应的TPositionMapper中的查询

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.enjoylearning.mybatis.mapper.TPositionMapper">
  <resultMap id="BaseResultMap" type="com.enjoylearning.mybatis.entity.TPosition">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="post_name" jdbcType="VARCHAR" property="postName" />
    <result column="note" jdbcType="VARCHAR" property="note" />
  </resultMap>
  <sql id="Base_Column_List">
    id, post_name, note
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_position
    where id = #{id,jdbcType=INTEGER}
  </select>
</mapper>  

嵌套查询会导致N+1问题,主要原因

  • 你执行了一个单独的SQL语句来获取结果列表(就是+1)
  • 对返回的每条记录,你执行了一个查询语句来加载细节,就是N
    这个问题会导致成百上千的SQL语句被执行,这通常不是期望的。

解决N+1问题办法就是开启懒加载、按需加载数据。开启懒加载的方式。
首先设置对应的查询语句

<association property="position" fetchType="lazy" column="position_id"
                     select="com.enjoylearning.mybatis.mapper.TPositionMapper.selectByPrimaryKey"/>

并将系统配置参数中aggressiveLazyLoading改为false

<settings>
    <!-- 设置自动驼峰转换		 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>

    <!-- 开启懒加载 -->
    <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。默认:true -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

一对多关联

collection支持的属性以及属性的作用和association完全相同,MyBatis会根据id标签,进行字段的合并,合理配置好id标签可以提高处理的效率。

@Test
// 1对多两种关联方式
public void testOneToMany() {
	// 2.获取sqlSession
	SqlSession sqlSession = sqlSessionFactory.openSession();
	// 3.获取对应mapper
	TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
	// 4.执行查询语句并返回结果
	// ----------------------
	List<TUser> selectUserJobs1 = mapper.selectUserJobs1();
	List<TUser> selectUserJobs2 = mapper.selectUserJobs2();
	for (TUser tUser : selectUserJobs1) {
		System.out.println(tUser);
	}
	for (TUser tUser : selectUserJobs2) {
		System.out.println(tUser.getJobs().size());
	}
}
package com.enjoylearning.mybatis.entity;

import java.io.Serializable;
import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.mysql.jdbc.Blob;

public class TUser implements Serializable{
	
    private Integer id;

    private String userName;

    private String realName;

    private Byte sex;

    private String mobile;

    private String email;

    private String note;

    private TPosition position;
    
    private List<TJobHistory> jobs ;
    
    private List<HealthReport> healthReports;

    
    private List<TRole> roles;

    //... setter getter 略
}    
List<TUser> selectUserJobs1();

List<TUser> selectUserJobs2();
<resultMap id="userAndJobs1" extends="BaseResultMap" type="com.enjoylearning.mybatis.entity.TUser">
    <collection property="jobs"
                ofType="com.enjoylearning.mybatis.entity.TJobHistory">
        <result column="comp_name" property="compName" jdbcType="VARCHAR"/>
        <result column="years" property="years" jdbcType="INTEGER"/>
        <result column="title" property="title" jdbcType="VARCHAR"/>
    </collection>
</resultMap>

<resultMap id="userAndJobs2" extends="BaseResultMap" type="com.enjoylearning.mybatis.entity.TUser">
    <collection property="jobs" fetchType="lazy" column="id"
                select="com.enjoylearning.mybatis.mapper.TJobHistoryMapper.selectByUserId"/>
</resultMap>

<select id="selectUserJobs1" resultMap="userAndJobs1">
    select a.id,
           a.userName,
           a.realName,
           a.sex,
           a.mobile,
           b.comp_name,
           b.years,
           b.title
    from t_user a,
         t_job_history b
    where a.id = b.user_id

</select>

<select id="selectUserJobs2" resultMap="userAndJobs2">
    select a.id,
           a.userName,
           a.realName,
           a.sex,
           a.mobile
    from t_user a
</select>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值