Mybatis

JDBC回顾

引入mysql依赖包后,进行jdbc六步的测试

package cn.itcast.jdbc;


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class JDBCTest {
    public static void main(String[] args) throws Exception {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 获取连接
            String url = "jdbc:mysql://127.0.0.1:3306/mybatis";
            String user = "root";
            String password = "root";
            conn = DriverManager.getConnection(url, user, password);
            // 获取statement,preparedStatement
            String sql = "select * from tb_user where id=?";
            ps = conn.prepareStatement(sql);
            // 设置参数
            ps.setLong(1, 1l);
            // 执行查询,获取结果集
            rs = ps.executeQuery();
            // 处理结果集
            while(rs.next()){
                System.out.println(rs.getString("user_name"));
                System.out.println(rs.getString("name"));
                System.out.println(rs.getInt("age"));
            }
        } finally {
            // 关闭连接,释放资源
            if(rs!=null){
                rs.close();
            }
            if(ps!=null){
                ps.close();
            }
            if(conn!=null){
                conn.close();
            }
        }
    }
}

缺点及解决方案
在这里插入图片描述

1.MyBatis介绍

1.1 简介

Mybatis前身是iBatis,是一个类似于Hibernate的ORM持久化框架,支持普通sql查询,存储过程以及高级映射
由于Mybatis是基于jdbc做了简单的映射包装,所以从性能来看:
JDBC > Mybatis > Hibernate

Hibernate的底层是session
JDBC和Mybatis底层是connection


1.2 架构

在这里插入图片描述

  1. 配置文件

    1. 全局配置文件:mybatis-config.xml,相当于Hibernate的hibernate.cfg.xml
      作用:配置数据源(数据库连接信息),引入映射文件

    2. 映射文件:XxMapper.xml,相当于Hibernate的xx.hbm.xml
      作用:配置sql语句、参数、结果集封装类型等

  2. SqlSessionFactory
    相当于Hibernate的SessionFactor
    作用:获取SqlSession
    通过newSqlSessionFactoryBuilder().build(inputStream)来构建,inputStream:读取配置文件的IO流

  3. SqlSession
    相当于Hibernate的Session
    作用:执行CRUD操作

  4. .Executor
    作用:执行器,SqlSession通过调用它来完成具体的CRUD
    它是一个接口,提供了两种实现:缓存的实现、数据库的实现

  5. Mapped Statement
    在映射文件里面配置,包含3部分内容:

    1. 具体的sql
    2. sql执行所需的参数类型
    3. sql执行结果的封装类型

    参数类型和结果集封装类型包括3种:

    1. HashMap
    2. 基本数据类型
    3. pojo

1.3 Mybatis的简单实现

1.引入mybatis的jar包

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

2.全局配置文件(mybatis-config.xml)
在这里插入图片描述
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>
    <!-- 环境:说明可以配置多个,default:指定生效的环境 -->
    <environments default="development">
        <!-- id:环境的唯一标识 -->
        <environment id="development">
            <!-- 事务管理器,type:类型 -->
            <transactionManager type="JDBC" />
            <!-- 数据源:type-池类型的数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis-49" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <!-- 映射文件 -->
    <mappers>
        <mapper resource="UserMapper.xml" />
    </mappers>
</configuration>

3.pojo中的User.java参照课前资料:
在这里插入图片描述
4.映射文件(UserMapper.xml)

<?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">
<!-- namespace(命名空间):映射文件的唯一标识 -->
<mapper namespace="UserMapper">

    <!-- 查询的statement,id:在同一个命名空间下的唯一标识,resultType:sql语句的结果集封装类型 -->
    <select id="queryUserById" resultType="User">
		select * from tb_user where id=#{id}
	</select>

</mapper>

5.编写mybatis程序(MybatisTest.java)

public static void main(String[] args) throws IOException {

    SqlSession sqlSession = null;
    try {
        // 指定mybatis的全局配置文件
        String resource = "mybatis-config.xml";
        // 读取mybatis-config.xml配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取sqlSession回话
        sqlSession = sqlSessionFactory.openSession();
        // 执行查询操作,获取结果集。参数:1-命名空间(namespace)+“.”+statementId,2-sql的占位符参数
        User user = sqlSession.selectOne("UserMapper.queryUserById", 1l);
        System.out.println(user);
    } finally {
        // 关闭连接
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

测试运行报错,找不到User类
在这里插入图片描述
原因:因为在映射文件中,返回类型不是全路径,无法通过反射去封装结果
在这里插入图片描述
执行流程
在这里插入图片描述

  1. 我们通过读取mybatis-config.xml配置文件,构建sqlSessionFactory
  2. 在初始化sqlSessionFactory时,会去读取映射文件,如果映射文件不存在,或者映射文件中内容有误,在初始化sqlSessionFactory就会报错
  3. 当sqlSessionFactory初始化成功后,就可以打开sqlSession会话,执行映射文件中的statement(这些内容都维护在sqlSessionFactory中)

问:映射文件是不是只能命名成XxMapper.xml?
答:不是,但我们一般默认这么命名,映射文件的命名其实是随意的,只要与配置文件中的<mappers>标签中名称一致即可,读取了映射文件后,会在sqlSessionFactory中维护每一个statment(namespace+statmentId作为唯一标识)

问:两个不同的XxMapper.xml,他们的namespace或者statmentId能否一致
答:namespace不能一样,statmentId可以一样(但同一个namespace中statmentId不能重复)


2.引入log日志

打印日志2个步骤:

  1. 在pom.xml中,引入slf4j的依赖
  2. 在src/main/resources目录下添加log4j.properties文件
2.1 引入日志依赖包(pom.xml)
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.6.6</version>
</dependency>
2.2 添加log4j.properties

在这里插入图片描述


3.MyBatis使用步骤总结

  1. 配置mybatis-config.xml 全局的配置文件 (1、数据源,2、外部的mapper)
  2. 创建SqlSessionFactory
  3. 通过SqlSessionFactory创建SqlSession对象
  4. 通过SqlSession操作数据库 CRUD
  5. 调用session.commit()提交事务
  6. 调用session.close()关闭会话

4.Mybatis完整的CRUD

在这里插入图片描述

4.1 UserDao接口的内容:
public interface UserDao {

	/**
	 * 根据id获取用户信息
	 * @param id
	 * @return
	 */
	public User queryUserById(Long id);
	
	/**
	 * 查询所有用户
	 * @return
	 */
	public List<User> queryUserAll();
	
	/**
	 * 新增用户
	 * @param user
	 */
	public void insertUser(User user);
	
	/**
	 * 更新用户信息
	 * @param user
	 */
	public void updateUser(User user);
	
	/**
	 * 根据id删除用户信息
	 * @param id
	 */
	public void deleteUserById(Long id);
}
4.2.创建UserDaoImpl

需要注入sqlSession

public class UserDaoImpl implements UserDao {
	
	private SqlSession sqlSession;
	
	public UserDaoImpl(SqlSession sqlSession){
		this.sqlSession = sqlSession;
	}

	@Override
	public User queryUserById(Long id) {
		return this.sqlSession.selectOne("UserDaoMapper.queryUserById", id);
	}

	@Override
	public List<User> queryUserAll() {
		return this.sqlSession.selectList("UserDaoMapper.queryUserAll");
	}

	@Override
	public void insertUser(User user) {
		this.sqlSession.insert("UserDaoMapper.insertUser", user);
		this.sqlSession.commit();
	}

	@Override
	public void updateUser(User user) {
		this.sqlSession.update("UserDaoMapper.updateUser", user);
		this.sqlSession.commit();
	}

	@Override
	public void deleteUserById(Long id) {
		this.sqlSession.delete("UserDaoMapper.deleteUserById", id);
		this.sqlSession.commit();
	}

}

注意:session在进行增伤改操作结束后,需要手动提交(跟数据库无关,mysql数据库也要手动提交)

4.3 编写UserDaoMapper.xml
  • 查询语句的statement标签是select,有resultType(返回类型)
  • 增删改是insert、update、delete,有parameterType(参数类型)
<?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="UserDaoMapper">

	<select id="queryUserById" resultType="cn.itcast.mybatis.pojo.User">
		select * from tb_user where id = #{id}
	</select>
	
	<select id="queryUserAll" resultType="cn.itcast.mybatis.pojo.User">
		select * from tb_user
	</select>
	
	<insert id="insertUser" parameterType="cn.itcast.mybatis.pojo.User">
		INSERT INTO tb_user (
			user_name,
			password,
			name,
			age,
			sex,
			birthday,
			created,
			updated
		)
		VALUES
			(
				#{userName},
				#{password},
				#{name},
				#{age},
				#{sex},
				#{birthday},
				NOW(),
				NOW()
			);
	</insert>
	
	<update id="updateUser" parameterType="cn.itcast.mybatis.pojo.User">
		UPDATE tb_user
		SET
		 user_name = #{userName},
		 password = #{password},
		 name = #{name},
		 age = #{age},
		 sex = #{sex},
		 birthday = #{birthday},
		 updated = NOW()
		WHERE
			(id = #{id});
	</update>
	
	<delete id="deleteUserById" parameterType="java.lang.Long">
		delete from tb_user where id=#{id}
	</delete>
	
</mapper>
4.4 引入UserDaoMapper.xml

在这里插入图片描述

4.5.测试UserDao

针对UserDao的测试,咱们使用Junit进行测试。

1、引入Junit依赖:参照父工程,在mybatis工程中的pom.xml中引入junit的依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

2.编写测试用例

public class UserDaoTest {
	
	private UserDao userDao;

	@Before
	public void setUp() throws Exception {
		
		String resource = "mybatis-config.xml";
		// 读取配置文件
		InputStream inputStream = Resources.getResourceAsStream(resource);
		// 构建sqlSessionFactory
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		// 获取sqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession();
		// 初始化userDao
		this.userDao = new UserDaoImpl(sqlSession);
	}

	@Test
	public void testQueryUserById() {
		User user = this.userDao.queryUserById(1l);
		System.out.println(user);
	}

	@Test
	public void testQueryUserAll() {
		List<User> users = this.userDao.queryUserAll();
		for (User user : users) {
			System.out.println(user);
		}
	}

	@Test
	public void testInsertUser() {
		User user = new User();
		user.setAge(18);
		user.setName("柳岩");
		user.setPassword("123456");
		user.setUserName("yanyan");
		user.setSex(3);
		user.setBirthday(new Date());
		this.userDao.insertUser(user);
	}

	@Test
	public void testUpdateUser() {
		// 查询
		User user = this.userDao.queryUserById(7l);
		// 更新
		user.setAge(28);
		user.setPassword("111111");
		this.userDao.updateUser(user);
	}

	@Test
	public void testDeleteUserById() {
		this.userDao.deleteUserById(7l);
	}

}
4.6 解决UserName为null

查询的时候,查不到userName的信息
原因是:数据库的字段名是user_name,而POJO中属性名是userName

在通过statement执行查询语句,获取到结果表后,会将结果表中数据封装到指定类型中,通过字段名匹配到对应的setXx方法
此处根据user_name查找到setUser_name

两端不一致,造成mybatis无法填充对应的字段信息。
修改方法:在sql语句中使用别名

修改映射文件(UserDaoMapper.xml)中的sql语句
在这里插入图片描述

5.动态代理Mapper实现类

复习我们上面学到的内容,我们在dao接口的实现类初始化的时候,注入sqlSession对象,通过sqlSession对象来操作数据库
但是我们发现dao接口实现类中就单单是调用了sqlSession的一系列方法,我们有没有办法把dao实现类省去?

5.1 思考CRUD中的问题
  1. 接口 > 实现类 > mapper.xml
  2. 实现类中,使用mybatis的方法非常类型
  3. sql statement硬编码到java代码中

思考:能否只写接口,不书写实现类,只编写Mapper.xml即可
因为在dao(mapper)的实现类中对sqlsession的使用方式很类似。mybatis提供了接口的动态代理

5.2 动态代理Mapper实现类,需要满足的要求
  • 映射文件的命名空间与Mapper接口的全路径一致
  • 映射文件中的statementId与Mapper接口的方法名一致
  • 映射文件中的statement的ResultType必须和mapper接口方法的返回类型一致(即使不采用动态代理,也要一致)
  • 映射文件中的statement的parameterType必须和mapper接口方法的参数类型一致(不一定,该参数可省略)
5.3 使用动态代理改造CRUD

在mybatis中,持久层的XxxDao通常习惯上命名为XxxMapper(例如:以前命名为UserDao的接口,现在命名为UserMapper)。
采用动态代理之后,只剩下UserMapper接口、UserMapper.xml映射文件以及UserMapper接口的测试文件,即可以少写一个接口的实现类

接口 > mapper.xml

(1) 创建接口

public interface UserMapper {
	/**
	 * 根据id获取用户信息
	 * @param id
	 * @return
	 */
	public User queryUserById(Long id);
	
	/**
	 * 查询所有用户
	 * @return
	 */
	public List<User> queryUserAll();
	
	/**
	 * 新增用户
	 * @param user
	 */
	public void insertUser(User user);
	
	/**
	 * 更新用户信息
	 * @param user
	 */
	public void updateUser(User user);
	
	/**
	 * 根据id删除用户信息
	 * @param id
	 */
	public void deleteUserById(Long id);
}

(2) UserMapper映射文件
名称空间必须改成UserMapper接口的全路径,StatementId必须和接口方法名一致,结果集的封装类型已经和方法的返回类型一致

<?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="cn.itcast.mybatis.mapper.UserMapper">

	<select id="queryUserById" resultType="cn.itcast.mybatis.pojo.User">
		select * from tb_user where id = #{id}
	</select>
	
	<select id="queryUserAll" resultType="cn.itcast.mybatis.pojo.User">
		select * from tb_user
	</select>
	
	<insert id="insertUser" parameterType="cn.itcast.mybatis.pojo.User">
		INSERT INTO tb_user (
			user_name,
			password,
			name,
			age,
			sex,
			birthday,
			created,
			updated
		)
		VALUES
			(
				#{userName},
				#{password},
				#{name},
				#{age},
				#{sex},
				#{birthday},
				NOW(),
				NOW()
			);
	</insert>
	
	<update id="updateUser" parameterType="cn.itcast.mybatis.pojo.User">
		UPDATE tb_user
		SET
		 user_name = #{userName},
		 password = #{password},
		 name = #{name},
		 age = #{age},
		 sex = #{sex},
		 birthday = #{birthday},
		 updated = NOW()
		WHERE
			(id = #{id});
	</update>
	
	<delete id="deleteUserById" parameterType="java.lang.Long">
		delete from tb_user where id=#{id}
	</delete>
	
</mapper>

可参考:mybatis动态代理原理:

(3) 通过sqlSession获取UserMapper

public class UserMapperTest {
	
	private UserMapper userMapper;

	@Before
	public void setUp() throws Exception {
		// 读取mybatis的全局配置文件
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		// 构建sqlSessionFactory
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		// 获取sqlSession会话
		SqlSession sqlSession = sqlSessionFactory.openSession();

		// 初始化userDao
		this.userMapper = sqlSession.getMapper(UserMapper.class);
	}

	@Test
	public void testQueryUserAll() {
		List<User> userList = this.userMapper.queryUserAll();
		for (User user : userList) {
			System.out.println(user);
		}
	}

}

注意:之前使用dao实现类,在进行增删改操作后,我们都会手动提交sqlSession,使用动态代理Mapper后,我们就不再直接使用sqlSession操作数据库(底层还是sqlSession),所以我们可以将sqlSession改为自动提交

不设置为自动提交,则增删改操作不会生效

SqlSession sqlSession = sqlSessionFactory.openSession(true);

6.mybatis-config.xml配置

到现在为止,我们主要在mybatis-config.xml中配置数据源和映射文件,他还有其他很多配置

mybatis-config.xml讲究严格的顺序,具体顺序遵循文档的顺序
在这里插入图片描述

6.1 properties属性读取外部资源

在这里插入图片描述
jdbc.properties资源文件内容:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis
username=root
password=root

在Mybatis-config.xml中引入jdbc.properties资源文件:

<?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>
	<!-- 引入外部资源文件,resource:相对路径,url:绝对路径 -->
	<properties resource="jdbc.properties" />
	<!-- 环境:说明可以配置多个,default:指定生效的环境 -->
	<environments default="development">
		<!-- id:环境的唯一标识 -->
		<environment id="development">
			<!-- 事务管理器,type:类型 -->
			<transactionManager type="JDBC" />
			<!-- 数据源:type-池类型的数据源 -->
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	<!-- 映射文件 -->
	<mappers>
		<mapper resource="UserMapper.xml" />
		<mapper resource="UserDaoMapper.xml" />
	</mappers>
</configuration>

通过properties引入外部资源文件之后,就可以通过${xxx}的方式使用资源文件里的参数了。


6.2 setting设置

settings参数有很多,咱们只需要学习以下4个参数就行了,今天咱们先学习驼峰匹配。

设置参数描述有效值默认值
cacheEnabled该配置影响的所有映射器中配置的缓存的全局开关。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。true | falsetrue
aggressiveLazyLoading当启用时,带有延迟加载属性的对象的加载与否完全取决于对任意延迟属性的调用;反之,每种属性将会按需加载。true | falsetrue
mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。true | falsetrue

开启驼峰匹配:完成经典的数据库命名到java属性的映射

  • 经典数据库命名:如果多个单词之间,通常使用下划线进行连接。
  • java中命名:第二个单词首字母大写。

驼峰匹配:相当于去掉数据中的名字的下划线,和java进行匹配

查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,POJO中的属性名字是userName,两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名
解决方案1:在sql语句中使用别名
解决方案2:参考驼峰匹配 — mybatis-config.xml

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>
	<!-- 引入外部资源文件,resource:classpath路径,url:绝对路径(不建议使用) -->
	<properties resource="jdbc.properties"></properties>
	<settings>
		<!-- 开启驼峰匹配:经典的数据库列名(多个单词下划线连接)映射到经典的java属性名(多个单词驼峰连接) -->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="UserMapper.xml" />
		<mapper resource="UserDaoMapper.xml" />
	</mappers>
</configuration>

在UserMapper.xml中修改sql语句删除别名
在这里插入图片描述
测试日志截图,如下:
在这里插入图片描述
删除sql中的别名,开启驼峰匹配后,仍然能够获取用户名,说明驼峰匹配起到了作用


6.3 typeAliases

之前咱们在映射文件中用到java类型时,都是使用类的全路径,书写起来非常麻烦
解决方案:
类型别名是为 Java 类型命名的一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。(官方文档)

方式一:typeAlias

<?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>
	<!-- 引入外部资源文件,resource:classpath路径,url:绝对路径(不建议使用) -->
	<properties resource="jdbc.properties"></properties>
	<settings>
		<!-- 开启驼峰匹配:经典的数据库列名(多个单词下划线连接)映射到经典的java属性名(多个单词驼峰连接) -->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	<typeAliases>
		<!-- 类型别名:type-pojo类的全路径,alias-别名名称(可随便写,推荐和类名一致) -->
		<typeAlias type="cn.itcast.mybatis.pojo.User" alias="user" />
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="UserMapper.xml" />
		<mapper resource="UserDaoMapper.xml" />
	</mappers>
</configuration>

缺点:每个pojo类都要去配置。

方式二:package
描指定包下的所有类,扫描之后的别名就是类名,大小写不敏感(不区分大小写),建议使用的时候和类名一致。

<typeAliases>
	<!-- 类型别名:type-pojo类的全路径,alias-别名名称(可随便写,推荐和类名一致) -->
	<!-- <typeAlias type="cn.itcast.mybatis.pojo.User" alias="user" /> -->
	<!-- 开启别名包扫描,name:包路径,扫描的别名就是类名,并且大小写不敏感 -->
	<package name="cn.itcast.mybatis.pojo"/>
</typeAliases>

在映射文件中使用类型别名:
在这里插入图片描述
已经为普通的 Java 类型内建了许多相应的类型别名。它们都是大小写不敏感的,需要注意的是由于重载原始类型的名称所做的特殊处理。
可参考mybatis

在这里插入图片描述

6.4 typeHandlers(类型处理器)

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。


6.5 plugins(插件又称拦截器)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
    在这里插入图片描述
    在这里插入图片描述
6.6.environments(环境)

MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置;
尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
虽然,这种方式也可以做到很方便的分离多个环境,但是实际使用场景下,我们更多的是选择使用spring来管理数据源,来做到环境的分离。

方法一:default
添加一个test(测试)环境,并在default参数中指向test环境。
在这里插入图片描述
方法二:build方法
在这里插入图片描述
在这里插入图片描述

6.7 Mappers

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。

方式一:resource
在mybatis-config.xml引入项目目录下的映射文件:
在这里插入图片描述
缺点:每次都要在mybatis-config.xml中引入映射文件,麻烦

方式二:file(不采用)
引入硬盘目录下的映射文件:
在这里插入图片描述
缺点:
1、硬盘的位置可能随着项目的部署或迁移,路径发生变化
2、每新增一个映射文件,就要在全局配置文件中引入

式三:class
在mybatis-config.xml配置mapper接口的全路径:
在这里插入图片描述
这种配置方式,在全局配置文件中配置了mapper接口的全路径,并没有配置mapper接口的映射文件的位置。如果要让mybatis找到对应的映射文件,则必须满足一定的条件或规则:
1、映射文件和mapper接口在同一个目录下
2、文件名必须一致
3、映射文件的namespace必须和mapper接口的全路径保持一致

在这里插入图片描述
缺点:
1、java文件和xml映射文件耦合
2、每新增一个映射文件,就要在全局配置文件中引入

方式四:package
这种方法是基于 class方法的,所以要求的条件也一样

在mybatis-config.xml中,开启包扫描:
在这里插入图片描述
原理:扫描目标包目录下的mapper接口,并按照class的方式找到接口对应的映射文件。
缺点:
1、如果包的路径有很多
2、mapper.xml和mapper.java没有分离。


8.Mapper XML 文件(映射文件)

在这里插入图片描述

8.1 CRUD标签

select
在这里插入图片描述
insert
在这里插入图片描述
update
在这里插入图片描述
delete
在这里插入图片描述

9.parameterType传入参数

CRUD标签都有一个属性parameterType,statement通过它指定接收的参数类型。
接收参数的方式有两种:

  1. #{}预编译
  2. ${}非预编译(直接的sql拼接,不能防止sql注入)

参数类型有三种:

  1. 基本数据类型
  2. HashMap(使用方式和pojo类似)
  3. Pojo自定义包装类型
9.1 ${}的用法

场景:数据库有两个一模一样的表。历史表,当前表
查询表中的信息,有时候从历史表中去查询数据,有时候需要去新的表去查询数据。希望使用1个方法来完成操作。
在UserMapper接口中,添加根据表名查询用户信息的方法:
在这里插入图片描述
在UserMapper映射文件中,添加方法对应的statement:
在这里插入图片描述
输出(报错):
在这里插入图片描述
注意:
使用${}去接收参数信息,在一个参数时,默认情况下必须使用${value}获取参数值,
而#{} 只是表示占位,与参数的名字无关,如果只有一个参数,可以使用任意参数名接收参数值,会自动对应。

但是这并不是一种稳妥的解决方案,推荐使用@Param注解指定参数名,所以以上接口及映射文件可以改成如下:
在这里插入图片描述
在这里插入图片描述
一个参数时,在使用#{}传参时,可以通过任意参数名接收参数;而${},默认必须通过value来接收参数。
可以通过@Param注解指定参数名

9.2 多个参数

当mapper接口要传递多个参数时,有两种传递参数的方法:

  1. 默认规则获取参数{0,1,param1,param2}
  2. 使用@Param注解指定参数名

案例:实现一个简单的用户登录,根据username和password验证用户信息
在UserMapper接口中,添加登陆方法:
在这里插入图片描述
在UserMapper.xml配置中,添加登陆方法对应的Statement配置:
在这里插入图片描述
在UserMapperTest测试用例中,添加测试方法:
在这里插入图片描述
运行报错
在这里插入图片描述
注意报错信息:没有找到参数userName,可用的参数是:[1,0,param1,param2],所以可有以下解决方案:

解决方案一:
在映射文件里,通过#{0},#{1}获取参数
在这里插入图片描述
解决方案二:
在映射文件里,通过param1,param2获取参数
在这里插入图片描述
最终解决方案:
在接口方法中的参数前,添加@Param注解指定参数名
在这里插入图片描述
在这里插入图片描述
通常在方法的参数列表上加上一个注解@Param(“xxxx”) 表示参数的名字,然后通过${“xxxx”}或#{“xxxx”}获取参数

注意:
单个参数时,#{}与参数名无关的。
多个参数时,#{} ${}与参数名(@Param)有关。

什么时候需要加@Param注解?什么时候不加?
单个参数不加,多个参数加
终极解决方案:都加。

9.3 HashMap

parameterType有三种类型的输入参数:

  • 1.基本数据类型
  • 2.hashMap
  • 3.pojo包装类

前面已经使用了基本数据类型和pojo类型的参数,那么hashMap这种类型的参数怎么传递参数呢?
其实,它的使用方式和pojo有点类似,简单类型通过#{key}或者${key},复杂类型通过${key.属性名}或者#{key.属性名}

UserMapper接口方法:
在这里插入图片描述
UserMapper配置文件:
在这里插入图片描述
在这里插入图片描述

9.4 面试题(#、$区别)

在这里插入图片描述
练习:根据用户名进行模糊查询
UserMapper接口:
在这里插入图片描述
UserMapper映射文件:
在这里插入图片描述


10. resultMap

在这里插入图片描述

10.1 解决列名和属性名不一致的情况

查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,而POJO中的属性名字是userName
两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名

解决方案1:在sql语句中使用别名
解决方案2:参考驼峰匹配 — mybatis-config.xml 的时候
解决方案3:resultMap自定义映射

在UserMapper.xml中配置resultMap
在这里插入图片描述
在UserMapper.xml中使用resultMap:
在这里插入图片描述

10.2 resultMap的自动映射

在上面讲到的resultMap中,主键需要通过id子标签配置,表字段和属性名不一致的普通字段需要通过result子标签配置。

那么,字段名称匹配的字段要不要配置那?这个取决于resultMap中的autoMapping属性的值:

  • 为true时:resultMap中的没有配置的字段会自动对应。
  • 为false时:只针对resultMap中已经配置的字段作映射。

并且resultMap会自动映射单表查询的结果集


11. sql片段

在java代码中,为了提高代码的重用性,针对一些出现频率较高的代码,抽离出来一个共同的方法或者类
那么针对一些重复出现的sql片段,mybatis有没有一个比较好的解决方案呢?
Mybatis当然想到了这一点,它就是sql标签。

11.1 用法一

sql标签可以定义一个sql片段,在需要使用该sql片段的地方,通过include标签来使用,如改造根据表名查询用户信息:
在这里插入图片描述

11.2 用法二

很多时候同一个sql片段,可能在很多映射文件里都有使用,这就需要添加一个映射文件,用来统一定义sql片段。
如下,在resource目录下新增CommonSQL.xml文件:
在这里插入图片描述
内容如下:

<?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">
<!-- namespace(命名空间):映射文件的唯一标识 -->
<mapper namespace="CommonSQL">

	<sql id="commonSql">
		id,user_name,
			password,
			name,
			age,
			sex,
			birthday,
			created,
			updated
	</sql>
</mapper>

定义好sql片段的映射文件之后,接下来就该使用它了,首先应该把该映射文件引入到mybatis的全局配置文件中(mybatis-config.xml):
在这里插入图片描述
最后在需要使用该sql片段的地方通过include标签的refId属性引用该sql片段:
在UserMapper.xml的映射文件中,进一步改造根据用户名查询用户信息
在这里插入图片描述

12.动态sql

在这里插入图片描述

12.1 if

判断语句
案例:查询男性用户,如果输入了用户名,按用户名模糊查询

在UserMapper接口中定义方法:

/**
 * 查询男性用户,如果输入了用户名,按用户名模糊查询
 * @param userName
 * @return
 */
public List<User> queryUserListLikeUserName(@Param("userName")String userName);

在UserMapper映射文件中,定义接口方法对应的Statement

<select id="queryUserListLikeUserName" resultType="User">
	select * from tb_user where sex=1
	<!-- if:判断
		test:OGNL表达式
	 -->
	<if test="userName!=null and userName.trim()!=''">
		 and user_name like '%' #{userName} '%'
	</if>
</select>

注意:在ognl表达式中,只有添加注解@param的参数才能获取


12.2 choose when otherwise

查询男性用户,如果输入了用户名则按照用户名模糊查找,否则如果输入了年龄则按照年龄查找,否则查找用户名为“zhangsan”的用户。

在UserMapper接口中,定义接口方法:

/**
* 查询男性用户,如果输入了用户名则按照用户名模糊查找,否则如果输入了年龄则按照年龄查找,否则查找用户名为“zhangsan”的用户。
 * @param userName
 * @param age
 * @return
 */
public List<User> queryUserListLikeUserNameOrAge(@Param("userName")String userName, @Param("age")Integer age);

在UserMapper.xml中,定义接口方法对应的Statement

<select id="queryUserListLikeUserNameOrAge" resultType="User">
	select * from tb_user where sex=1 
	<!-- choose:条件选择
		when:test-判断条件,一旦有一个when成立,后续的when都不再执行
		otherwise:所有的when都不成立时,才会执行
	 -->
	<choose>
		<when test="userName!=null and userName.trim()!=''">and user_name like '%' #{userName} '%'</when>
		<when test="age != null">and age = #{age}</when>
		<otherwise>and user_name = 'zhangsan' </otherwise>
	</choose>
</select>

12.3 where

案例:查询所有用户,如果输入了用户名按照用户名进行模糊查询,如果输入年龄,按照年龄进行查询,如果两者都输入,两个条件都要成立。

在UserMapper接口中,定义接口方法:

/**
 * 查询所有用户,如果输入了用户名按照用户名进行模糊查询,如果输入年龄,按照年龄进行查询,如果两者都输入,两个条件都要成立。
 * @param userName
 * @param age
 * @return
 */
public List<User> queryUserListLikeUserNameAndAge(@Param("userName")String userName, @Param("age")Integer age);

在UserMapper.xml中,定义接口方法对应的Statement

<select id="queryUserListLikeUserNameAndAge" resultType="User">
	select * from tb_user
	<!-- 
		自动添加where关键字
		有一定的纠错功能:去掉sql语句块之前多余的一个and|or
		通常结合if或者choose使用
	 -->
	<where>
		<if test="userName!=null and userName.trim()!=''">user_name like '%' #{userName} '%'</if>
		<if test="age!=null">and age = #{age}</if>
	</where>
</select>

12.4 set

案例:修改用户信息,如果参数user中的某个属性为null,则不修改。

在UserMapper接口中,定义接口方法:

/**
 * 修改用户信息,如果参数user中的某个属性为null,则不修改。
 * @param user
 */
public void updateUserSelective(User user);

在UserMapper.xml中,定义接口方法对应的Statement

<update id="updateUserSelective" >
	UPDATE tb_user
	<!-- 
		set自动添加set关键字
		也有一定的纠错功能:自动去掉sql语句块之后多余的一个逗号
	 -->
	<set>
		<if test="userName!=null and userName.trim()!=''">user_name = #{userName},</if>
		<if test="password!=null and password.trim()!=''">password = #{password},</if>
		<if test="name!=null and name.trim()!=''">name = #{name},</if>
		<if test="age!=null">age = #{age},</if>
		<if test="sex!=null">sex = #{sex}</if>
	</set>
	WHERE
		(id = #{id});
</update>

12.5 foreach

案例:按照多个id查询用户信息

在UserMapper接口中,定义接口方法:

/**
 * 根据多个id查询用户信息
 * @param ids
 * @return
 */
public List<User> queryUserListByIds(@Param("ids")Long[] ids);

在UserMapper.xml中,定义接口方法对应的Statement

<select id="queryUserListByIds" resultType="User">
	select * from tb_user where id in 
	<!-- 
		foreach:遍历集合
		collection:接收的集合参数
		item:遍历的集合中的一个元素
		separator:分隔符
		open:以什么开始
		close:以什么结束
	 -->
	<foreach collection="ids" item="id" separator="," open="(" close=")">
		#{id}
	</foreach>
</select>

13 缓存

执行相同的sql语句和参数,mybatis不进行执行sql,而是从缓存中命中返回。

13.1 一级缓存

(在hibernate中一级缓存的作用域是session)

在mybatis中,一级缓存默认是开启的,并且一直无法关闭,作用域:在同一个sqlSession下

@Test
public void testCache(){
	User user1 = this.userMapper.queryUserById(1l);
	System.out.println(user1);
	System.out.println("=================第二次查询======================");
	User user2 = this.userMapper.queryUserById(1l);
	System.out.println(user2);
}

由于一级缓存的存在,此时在log日志中,应该只会在第一次查询是执行sql语句,第二次查询时直接从缓存中命中,即不再执行sql语句。
在这里插入图片描述
一级生效缓存的条件: 在同一个sqlSession下,执行相同的sql语句和参数(中间不能有增删改操作)
执行update、insert、delete的时候,会清空缓存

@Test
public void testCache() {
    User user1 = userMapper.queryUserById(1l);
    System.out.println(user1);

    // 新增用户
    User user = new User();
    user.setUserName("王晓峰");
    userMapper.insertUser(user);

    System.out.println("----------------第二次查询----------------");
    User user2 = userMapper.queryUserById(1l);
    System.out.println(user2);
}

在这里插入图片描述

13.2 二级缓存

mybatis 的二级缓存的作用域:

  1. 同一个mapper的namespace,同一个namespace查询的sql可以从缓存中命中(一个mapper只有一个namespace)
  2. 跨sqlSession,不同的sqlSession可以从二级缓存中命中

怎么开启二级缓存

  1. 在映射文件中,添加<cache />标签
  2. 在全局配置文件中,设置cacheEnabled参数,默认已开启。

注意:
由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,所以第一个sqlSession必须先关闭
二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。

开启二级缓存,在映射文件(UserMapper.xml)中添加:
在这里插入图片描述
在UserMapperTest中添加二级缓存的测试方法:

@Test
public void testCache2(){
	User user1 = this.userMapper.queryUserById(1l);
	System.out.println(user1);
	// 注意:关闭sqlSession
	sqlSession.close();
	System.out.println("=================第二次查询======================");
	// 重新打开一个sqlSession会话
	SqlSession sqlSession2 = this.sqlSessionFactory.openSession();
	// 通过sqlSession2重新实例化UserMapper
	this.userMapper = sqlSession2.getMapper(UserMapper.class);
	User user2 = this.userMapper.queryUserById(1l);
	System.out.println(user2);
}

运行测试用例:
在这里插入图片描述
说明二级缓存,必须序列化,使User类实现序列化接口:
在这里插入图片描述
给User对象实现序列化接口后,重新运行测试用例,日志:
在这里插入图片描述
执行update、insert、delete的时候,会清空缓存

@Test
public void testCache2(){
	User user1 = this.userMapper.queryUserById(1l);
	System.out.println(user1);
	
	// 注意:关闭sqlSession
	sqlSession.close();
	System.out.println("=======================================");
	// 重新打开一个sqlSession会话
	SqlSession sqlSession2 = this.sqlSessionFactory.openSession();
	// 通过sqlSession2重新实例化UserMapper
	this.userMapper = sqlSession2.getMapper(UserMapper.class); 		
	User user = new User();
	user.setAge(18);
	user.setName("柳岩");
	user.setPassword("123456");
	user.setUserName("yanyan2");
	user.setBirthday(new Date());
	user.setId(12l);
	this.userMapper.updateUserSelective(user);
	System.out.println("=======================================");
	User user2 = this.userMapper.queryUserById(1l);
	System.out.println(user2);
}

关闭二级缓存:
不开启,或者在全局的mybatis-config.xml 中去关闭二级缓存
在这里插入图片描述
在mybatis-config.xml配置中:

<settings>
	<!-- 行为参数,name:参数名,value:参数值,默认为false,true:开启驼峰匹配,即从经典的数据库列名到经典的java属性名 -->
	<setting name="mapUnderscoreToCamelCase" value="true"/>
	<!-- 关闭二级缓存,默认是开启,false:关闭 -->
	<setting name="cacheEnabled" value="false"/>
</settings>

在这里插入图片描述

13.3 缓存总结

一级缓存

  • 开启条件:默认开启,无法关闭
  • 作用条件:同一个sqlSession,执行相同的sql语句和参数,第二次不执行sql,而是从命中中获取
  • 清空缓存
    • 1.sqlSession.clearCache();可以强制清除缓存
    • 2.执行update、insert、delete的时候,会清空缓存

因为mybatis没有像hibernate的快照机制,所以当执行增删改操作后,会清空缓存

二级缓存

  • 开启条件
    • 1.在映射文件中,添加<cache />标签
    • 2.在全局配置文件中,设置cacheEnabled参数,默认已开启。
  • 作用条件:
    • 1.跨sqlSession,不同的SqlSession在同一个mapper的namespace中查询相同sql可以从缓存中命中。
    • 2.由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,所以第一个sqlSession必须先关闭
    • 3.二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。
  • 清空缓存
    • 执行update、insert、delete的时候,会清空缓存

14.高级查询

14.1 一对一查询

需求:查询出订单信息,并查询出下单人信息

在这里插入图片描述
结果如下:
在这里插入图片描述

获取的结果中有很多数据,无法将其封装到User中

扩展Order类的实现方式(了解)
创建新的pojo对象OrderUser继承Order对象,这样OrderUser就从Order中继承了订单信息。再把User对象中的属性copy到OrderUser对象中,这样OrderUser又有了User中的用户信息。把sql语句的结果集封装成OrderUser对象,那么结果中就既包含了订单信息又包含了下单人信息。

新建OrderUser实体类继承Order,在把User中的属性copy到OrderUser中,并添加get、set方法:
在这里插入图片描述
在OrderMapper接口中,添加查询方法:
在这里插入图片描述
在OrderMapper.xml中,配置对应的Statement,并把结果集封装成OrderUser对象:
在这里插入图片描述
在OrderMapperTest中添加测试方法:
在这里插入图片描述

添加User属性的实现方式
思路:
从订单的角度来看,订单和用户的关系是一对一的关系,并且通过tb_order表中的id进行关联。
用面向对象的思想实现,就是在Order对象中添加user属性。

在这里插入图片描述
在OrderMapper接口中,添加接口方法:

public interface OrderMapper {
    /**
     * 根据订单号查询订单信息,并且查询出下单人信息
     * @param number
     * @return
     */
    public Order queryOrderWithUser(@Param("number") String number);
}

使用resultType不能完成user对象的自动映射,需要手动完成结果集映射,即使用resultMap标签自定义映射。
在OrderMapper.xml中配置,结果集的映射,这时必须使用resultMap:

<resultMap type="Order" id="orderUserMap" autoMapping="true">
	<id column="id" property="id"/>
	<!-- 
		association:一对一的映射
		property:java的属性名
		javaType:属性名对应的java类型
		autoMapping:开启自动映射
		子标签:参照resultMap
	 -->
	<association property="user" javaType="User" autoMapping="true">
		<id column="user_id" property="id"/>
	</association>
</resultMap>

<!-- resultType不能完成user信息的映射,必须使用resultMap,resultMap的值对应resultMap标签的id,resultMap和resultType必须二选一 -->
<select id="queryOrderWithUser" resultMap="orderUserMap">
	select * from tb_order a 
		LEFT JOIN tb_user b on a.user_id=b.id
	where a.order_number = #{number}
</select>

在OrderMapperTest添加测试

public class OrderMapperTest {
	
	private OrderMapper orderMapper;

	@Before
	public void setUp() throws Exception {
		String resource = "mybatis-config.xml";
		// 读取配置文件
		InputStream inputStream = Resources.getResourceAsStream(resource);
		// 构建sqlSessionFactory
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "development");
		
		// 获取sqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession(true);
		
		this.orderMapper = sqlSession.getMapper(OrderMapper.class);
	}

	@Test
	public void testQueryOrderWithUser() {
		System.out.println(this.orderMapper.queryOrderWithUser("20140921001"));
	}

}

14.2 一对多查询

一对多查询:查询订单,查询出下单人信息并且查询出订单详情。

思路:
订单 : 订单详情 = 1 : n(体现在pojo对象中,就是在Order对象中添加OrderDetail对象的集合)
在这里插入图片描述
在Order类添加List属性,并添加get、set方法:
在这里插入图片描述
OrderMapper接口

/**
 * 查询订单,查询出下单人信息并且查询出订单详情。
 * @param number
 * @return
 */
public Order queryOrderWithUserDetail(@Param("number")String number);
<resultMap type="Order" id="orderUserDetailMap" autoMapping="true">
	<id column="id" property="id"/>
	<association property="user" javaType="User" autoMapping="true">
		<id column="user_id" property="id"/>
	</association>
	<!-- 
		collection:一对多的查询
		property:属性名
		javaType:集合类型
		ofType:集合中的元素类型
		autoMapping:开启自动映射
		子标签:参照resultMap
	 -->
	<collection property="detailList" javaType="list" ofType="Orderdetail" autoMapping="true">
		<id column="detail_id" property="id"/>
	</collection>
</resultMap>

<select id="queryOrderWithUserDetail" resultMap="orderUserDetailMap">
	select *,c.id as detail_id from tb_order a
		LEFT JOIN tb_user b on a.user_id=b.id
		LEFT JOIN tb_orderdetail c on a.id=c.order_id
	where a.order_number=#{number}
</select>

OrderMapperTest测试:

@Test
public void testQueryOrderWithUserDetail(){
	System.out.println(this.orderMapper.queryOrderWithUserDetail("20140921001"));
}

14.3 多对多查询

多对多查询:查询订单,查询出下单人信息并且查询出订单详情中的商品数据。

思路:
订单:订单详情 = 1 : n(体现在pojo对象中就是在Order对象中添加OrderDetail对象的集合)
订单详情:商品 = 1 : 1(体现在pojo对象中就是在OrderDetail对象中添加Item对象)

Sql:
在这里插入图片描述
在Orderdetail添加Item属性,并添加get、set方法:
在这里插入图片描述
OrderMapper接口:

/**
	 * 查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
	 * @param number
	 * @return
	 */
public Order queryOrderWithUserDetailItem(@Param("number")String number);

OrderMapper配置(通过在collection标签中嵌套使用association标签):

<resultMap type="Order" id="orderUserDetailItemMap" autoMapping="true">
	<id column="id" property="id"/>
	<association property="user" javaType="User" autoMapping="true">
		<id column="user_id" property="id"/>
	</association>
	<collection property="detailList" javaType="list" ofType="Orderdetail" autoMapping="true">
		<id column="detail_id" property="id"></id>
		<association property="item" javaType="Item" autoMapping="true">
			<id column="item_id" property="id"/>
		</association>
	</collection>
</resultMap>

<select id="queryOrderWithUserDetailItem" resultMap="orderUserDetailItemMap">
	select *,c.id as detail_id from tb_order a
		LEFT JOIN tb_user b on a.user_id=b.id
		LEFT JOIN tb_orderdetail c on a.id=c.order_id
		LEFT JOIN tb_item d on c.item_id=d.id
	where a.order_number=#{number}
</select>

OrderMapperTest测试:

@Test
public void testQueryOrderWithUserDetailItem(){
	System.out.println(this.orderMapper.queryOrderWithUserDetailItem("20140921001"));
}

15.resultMap的继承

在这里插入图片描述

16.高级查询的整理

resutlType无法帮助我们自动的去完成映射,所以只有使用resultMap手动的进行映射
resultMap:
type 结果集对应的数据类型
id 唯一标识,被引用的时候,进行指定
autoMapping 开启自动映射
extends 继承
子标签:
association:一对一的映射
property 定义对象的属性名
javaType 属性的类型
autoMapping 开启自动映射
collection:一对多的映射
property 定义对象的属性名
javaType 集合的类型
ofType 集合中的元素类型
autoMapping 开启自动映射


17.延迟加载

设置参数描述有效值默认值
cacheEnabled该配置影响的所有映射器中配置的缓存的全局开关。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。true | falsetrue
aggressiveLazyLoading当启用时,带有延迟加载属性的对象的加载与否完全取决于对任意延迟属性的调用;反之,每种属性将会按需加载。true | falsetrue
mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。true | falsetrue

分析:
采用之前的配置方式(参考高级查询),肯定是不能做到延迟加载的,因为咱是通过一个查询sql直接查询出所有的数据。为了测试延迟加载的效果,必须改造高级查询的配置,使Order的查询和User或者OrderDetail的查询分开。只有当我们访问Order对象的User或者OrderDetail属性时,才去执行User或者OrderDetail的查询。

17.1 改造一对一查询

在OrderMapper接口中,编写接口方法:

/**
* 测试延迟加载
 * @param number
 * @return
 */
public Order queryOrderLazy(@Param("number")String number);

OrderMapper配置:
在这里插入图片描述
处理组合键时,需要传递多个参数,可以使用column=”{prop1=col1, prop2=col2, prop3=col3…}”,设置多个列名传入到嵌套查询语句,mybatis会把prop1,prop2,prop3设置到目标嵌套的查询语句中的参数对象中。
子查询中,必须通过prop1,prop2,prop3获取对应的参数值,你也可以使用这种方式指定参数名例如:
在这里插入图片描述
测试:

@Test
public void queryOrderLazy(){
    Order order = this.orderMapper.queryOrderLazy("20140921001");
}

执行结果如下:我们发现并没有延时加载,这是因为我们还没有开启延时加载
在这里插入图片描述

17.2 开启延时加载

在mybatis-config.xml中配置行为参数:
在这里插入图片描述
修改测试用例,注释掉打印Order信息的这行代码:

在这里插入图片描述
执行,报错:
在这里插入图片描述
说明延迟加载需要cglib的支持。
在pom.xml中,添加cglib的依赖:

<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.1</version>
</dependency>

执行:
在这里插入图片描述

MyBatis——懒加载:lazyLoadingEnabled和aggressiveLazyLoading的使用


18.如果sql语句中出现’<’的解决方案

使用xml中的字符实体
在这里插入图片描述
使用<![CDATA[ < ]]>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值