文章目录
前言
本系列教程不是初级教程,需要对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编程存在的弊端
- 工作量大,操作数据库至少需要5步
- 业务代码和技术代码耦合
- 连接资源手动关闭,带来了隐患
二、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);
}
核心类分析:
- SqlSessionFactoryBuilder:读取配置信息创建SqlSessionFactory,建造者模式,方法级别声明周期
- SqlSessionFactory:创建SqlSession,工厂单例模式,存在于程序的整个生命周期
- SqlSession:代表一次数据库连接,一般通过调用Mapper访问数据库,也可以直接发送SQL执行;线程不安全,要保证线程独享(方法级别)
- 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元素
属性 | 描述 |
---|---|
keyProperty | selectKey语句结果应该被设置的目标属性,如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表 |
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. 关联查询
关联查询几个需要注意的细节
- 超过三个表禁止join,需要join的字段,数据类型必须绝对一致,多表关联查询时,保证被关联的字段有索引
- 不得使用外键与级联,一切外键概念必须在应用层中解决
- 字段允许适当冗余,以提高查询性能,但必须考虑数据一致
为什么超过三个表禁止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>