mybatis

文章目录

框架

三层架构

  • 表现层:用于展示数据
  • 业务层:处理业务需求
  • 持久层:和数据库交互

在这里插入图片描述
持久层技术解决方案

  • JDBC技术:

    • Connection、PreparedStatement、ResultSet
  • Spring的JDBCTemplate:

    • Spring对jdbc的简单封装
  • Apache的DBUtils:

    • 与Spring的JDBCTemplate很像,也是对JDBC的简单封装

但以上这些都不是框架,JDBC是规范,Spring的JDBCTemplate和Apache的DBUtils都只是工具类。

Mybatis

JDBC 编程的分析回顾

public static void main(String[] args) {
	Connection connection = null;
	PreparedStatement preparedStatement = null;
	ResultSet resultSet = null;
	try {
	//加载数据库驱动
	Class.forName("com.mysql.jdbc.Driver");
	//通过驱动管理类获取数据库链接
	connection = DriverManager
	.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","ro
	ot", "root");
	//定义 sql 语句 ?表示占位符
	String sql = "select * from user where username = ?";
	//获取预处理 statement
	preparedStatement = connection.prepareStatement(sql);
	//设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的
	参数值
	preparedStatement.setString(1, "王五");
	//向数据库发出 sql 执行查询,查询出结果集
	resultSet = preparedStatement.executeQuery();
	//遍历查询结果集
	while(resultSet.next()){
		System.out.println(resultSet.getString("id")+"
		"+resultSet.getString("username"));
		}
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		//释放资源
		if(resultSet!=null){
			try {
				resultSet.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if(preparedStatement!=null){
			try {
				preparedStatement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if(connection!=null){
			try {
				connection.close();
			} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			}
		}
	}
}
	上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。

JDBC问题分析

1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

2、 Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大, sql 变动需要改变 java代码。

3、 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。

4、 对结果集解析存在硬编码(查询列名), sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。

MyBatis 框架概述

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中
sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。

采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。

在这里插入图片描述

mybatis的入门

mybatis环境搭建

  • 第一步:创建maven工程并导入坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>day01_eesy_01mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <!-- 导入mybatis坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!-- 导入mysql坐标-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <!-- 导入日志信息坐标-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
    </dependencies>


</project>
  • 创建实体类和dao的接口
package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private  Integer id;
    private  String username;
    private  Date birthday;
    private  String sex;
    private  String address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

package com.itheima.dao;

import com.itheima.domain.User;

import java.util.List;

//用户的持久层接口
public interface IUserDao {
    List<User> findAll();
}

  • 创建Mybatis的主配置文件SqlMapConfig.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">
<!--mybatis的主配置文件-->
<configuration>
    <!--  配置环境  -->
    <environments default="mysql">
        <!--  配置mysql环境  -->
        <environment id="mysql">
        <!-- 配置事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/itheima/dao/IUserDao.xml"/>
    </mappers>
</configuration>
  • 创建映射配置文件IUserDao.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">
 <mapper namespace="com.itheima.dao.IUserDao">
<!-- 配置查询所有,并将查询结果封装成User对象,放在list集合中(resultType:结果类型)-->
    <select id="findAll" resultType="com.itheima.domian.USer">
        select * from user
    </select>
</mapper>

在这里插入图片描述
编写持久层接口的映射文件 IUserDao.xml要求:

  • 创建位置:必须和持久层接口在相同的包中
  • 名称:必须以持久层接口名称命名文件名,扩展名为.xml
    在这里插入图片描述

mybatis入门案例

  • 第一步:读取配置文件
  • 第二步:创建SqlSessionFactory工厂
  • 第三步:使用工厂生产SqlSession对象
  • 第四步:使用SqlSession创建Dao接口的代理对象
  • 第五步:使用代理对象执行方法
  • 第六步:释放资源
//编写测试类
package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import jdk.internal.loader.Resource;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

public class Mybatis {
    public static void main(String[] args) throws Exception{
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //6.释放资源
        session.close();
        in.close();
    }
}

注意:不要忘记在映射配置中告知mybatis要封装到哪个实体类中。配置的方式:指定实体类的全限定类名。如下IUserDao.xml:

<mapper namespace="com.itheima.dao.IUserDao">
<!--    配置查询所有,并将查询结果封装成User对象,放在list集合中(resultType:结果类型)-->
<select id="findAll" resultType="com.itheima.domain.User">
        select * from user
    </select>
</mapper>

mybatis基于注解的入门案例

把IUserDao.xml移除,在dao接口的方法上使用@Select注解,且指定SQL语句。同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。如下所示:

<?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">
<!-- mybatis的主配置文件 -->
<configuration>
    <!-- 配置环境 -->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的4个基本信息 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件
         如果使用注解来配置的话,此处应使用class属性指定被注解的dao全限定类名
    -->
    <mappers>
    <!--<mapper resource="com/itheima/dao/IUserDao.xml"/>-->
        <mapper class = "com.itheima.dao.IUserDao"></mapper>
    </mappers>
</configuration>

明确:mybatis也是可以用dao实现类的,但在实际开发中,都是越简便越好,无论是xml还是注解的形式。

细节分析

在这里插入图片描述

自定义mybatis的分析

mybatis在使用代理dao的方式实现增删改查时做什么事?只有两件事。

  • 第一:创建代理对象
  • 第二:在代理对象中调用selectList方法

执行查询所有的分析+查询代理对象的分析

在这里插入图片描述

自定义mybatis能通过入门案例看到的类

class Resources
class SqlSessionFactoryBuilder
interface SqlSessionFactory
interface SqlSession

自定义mybatis

在这里插入图片描述

基于代理 Dao 实现 CRUD 操作

使用要求:
1、持久层接口和持久层接口的映射配置必须在相同的包下
2、持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
3、 SQL 语句的配置标签,,,的 id 属性必须和持久层接口的
方法名相同。

如上依次配置mybatis环境,然后在持久层接口中添加 CRUD 方法:

package com.itheima.dao;

import com.itheima.domain.User;

import java.util.List;

//用户的持久层接口
public interface IUserDao {
    //查询所有
    List<User> findAll();
    //保存用户
    void saveUser(User user);
    //更新用户
    void updateUser(User user);
    //删除用户
    void deleteUser(Integer userId);
    //通过id查询用户
    User findById(Integer userId);
    //根据名称模糊查询用户
    List<User> findByName(String userName);
    //查询总用户数
    int findTotal();
}

接着,在用户的映射配置文件中配置:

<?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指的是哪个方法的接口,id指的是哪个方法,resultType指的是封装到哪里去-->
<mapper namespace="com.itheima.dao.IUserDao">
<!--查询所有-->
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from user;
    </select>
<!--保存用户-->
    <insert id="saveUser" parameterType="com.itheima.domain.User">
--     配置插入操作后获取插入数据的id:
--         keyProperty-属性名称,对应实体类
--         keyColumn-id的列名,对应数据库的表
--         resultType-结果集类型
--         order-何时执行获取结果集的操作,AFTER意为插入之后
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into user (username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
    </insert>
<!--更新用户-->
    <update id="updateUser" parameterType="com.itheima.domain.User">
        update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
    </update>
<!--删除用户,由于传入的是单参,因此下面的uid可以任意命名-->
    <delete id="deleteUser" parameterType="Integer">
        delete from user where id = #{uid}
    </delete>
<!--通过id查询用户-->
    <select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">
        select * from user where id = #{uid};
    </select>
<!--根据名称模糊查询用户-->
    <select id="findByName" parameterType="String" resultType="com.itheima.domain.User">
        select * from user where username like #{name};
        <!--select * from user where username like '%${value}%'-->
    </select>
<!--查询总用户数-->
    <select id="findTotal" resultType="Integer">
        select count(id) from user;
    </select>
</mapper>
细节:
resultType 属性:用于指定结果集的类型。
parameterType 属性:用于指定传入参数的类型。
sql 语句中使用#{}字符:它代表占位符, 相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的
#{}中内容的写法:
	(1)如果数据类型是基本类型,此处可以随意写。
	(2)由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。
它用的是 ognl 表达式
	ognl 表达式:
		它是 apache 提供的一种表达式语言, 全称是:
		Object Graphic Navigation Language 对象图导航语言
		它是按照一定的语法格式来获取数据的。
		语法格式就是使用 #{对象.对象}的方式
		#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写 username。

最后,在测试类添加测试:

package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

//测试mybatis的CRUD
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws IOException{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
       userDao = sqlSession.getMapper(IUserDao.class);
    }
    @After//用于在测试方法执行之后执行
    public void destroy() throws IOException {
        //提交事务(避免发生回滚)
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }
    // 测试查询所有
    @Test
    public void testFindAll(){
        //5.执行查询所有方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }

    }
    //测试保存用户
    @Test
    public void testSave(){
        User user = new User();
        user.setUsername("mybatis last insertid");
        user.setAddress("山东省淄博市");
        user.setSex("女");
        user.setBirthday(new Date());
        System.out.println("保存之前"+user);
        //5.执行保存方法
        userDao.saveUser(user);
        System.out.println("保存之后"+user);
    }

    //测试更新用户
    @Test
    public void testUpdate(){
        User user = new User();
        user.setId(50);
        user.setUsername("mybatis Update User");
        user.setAddress("山东省淄博市");
        user.setSex("女");
        user.setBirthday(new Date());

        //5.执行更新方法
        userDao.updateUser(user);
    }

    //测试删除用户
    @Test
    public void testDelete(){

        //5.执行删除方法
        userDao.deleteUser(50);
    }

    //测试删除用户
    @Test
    public void testFindOne(){

        //5.通过id查询用户
        User user = userDao.findById(48);
        System.out.println(user);
    }

    //测试模糊查询用户
    @Test
    public void testFindByName(){

        //5.根据名称模糊查询用户
        List<User> users = userDao.findByName("%王%");
        //List<User> users = userDao.findByName("王");
        for (User user : users) {
            System.out.println(user);
        }
    }

    //测试查询用户总数
    @Test
    public void testFindTotal(){

        //5.测试查询用户总数
        int count = userDao.findTotal();
        System.out.println(count);
    }

}


模糊查询的两种配置方式:

  • 第一种(占位符号-推荐使用):
<!-- 根据名称模糊查询 -->
<select id="findByName" resultType="com.itheima.domain.User" parameterType="String">
select * from user where username like #{username}
</select>
@Test
public void testFindByName(){
	//5.执行查询一个方法
	List<User> users = userDao.findByName("%王%");
	for(User user : users){
	System.out.println(user);
	}
}

在控制台输出的执行 SQL 语句如下:
在这里插入图片描述

我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。 配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”。

  • 第二种(拼接 sql 串-了解):

第一步: 修改 SQL 语句的配置,配置如下:

<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
select * from user where username like '%${value}%'
</select>

我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。

第二步:测试,如下:

/**
* 测试模糊查询操作
*/
@Test
public void testFindByName(){
	//5.执行查询一个方法
	List<User> users = userDao.findByName("王");
	for(User user : users){
	System.out.println(user);
	}
}

在控制台输出的执行 SQL 语句如下:
在这里插入图片描述
可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。

#{}与${}的区别

#{}表示一个占位符号

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

${}表示拼接 sql 串

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

问题扩展:新增用户 id 的返回值

新增用户后, 同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
在这里插入图片描述

Mybatis 与 JDBC 编程的比较

1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。

2、Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大, sql 变动需要改变 java 代码。

解决:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。

3、向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数对应。

解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。

4、对结果集解析麻烦, sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。

解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型。

mybatis的参数深入

parameterType 配置参数

SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是

  • 基本类型
  • 引用类型(例如:String 类型)
  • 实体类类型( POJO 类)
  • 使用实体类的包装类,下面将介绍如何使用实体类的包装类作为参数传递。

基 本 类 型 和 String 我 们 可 以 直 接 写 类 型 名 称 , 也 可 以 使 用 包 名 . 类 名 的 方 式 , 例 如 :java.lang.String。
实体类类型,目前我们只能使用全限定类名。
究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名

开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。

编写 QueryVo

/**
*
* <p>Title: QueryVo</p>
* <p>Description: 查询条件对象</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class QueryVo implements Serializable {
	private User user;
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
}

编写持久层接口

/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的业务层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
/**
* 根据 QueryVo 中的条件查询用户
* @param vo
* @return
*/
	List<User> findByVo(QueryVo vo);
}

持久层接口的映射文件

<!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
<select id="findByVo" resultType="com.itheima.domain.User"
					  parameterType="com.itheima.domain.QueryVo">
	select * from user where username like #{user.username};
</select>

测试包装类作为参数

@Test
public void testFindByQueryVo() {
	QueryVo vo = new QueryVo();
	User user = new User();
	user.setUserName("%王%");
	vo.setUser(user);
	List<User> users = userDao.findByVo(vo);
	for(User u : users) {
		System.out.println(u);
	}
}

resultType 配置结果类型

  • 输出简单类型
  • 输出pojo对象
  • 输出pojo列表

resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。我们在前面的 CRUD 案例中已经对此属性进行过应用了。需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)

同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

详细实例:

输出简单类型实例

Dao 接口

/**
* 查询总记录条数
* @return
*/
int findTotal();

映射配置

<!-- 查询总记录条数 -->
<select id="findTotal" resultType="int">
	select count(*) from user;
</select>
实体类类型示例

Dao 接口

/**
* 查询所有用户
* @return
*/
List<User> findAll();

映射配置

<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
	select * from user
</select>
特殊情况示例

修改实体类

实体类代码如下: (此时的实体类属性和数据库表的列名已经不一致了)
/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
	private Integer userId;
	private String userName;
	private Date userBirthday;
	private String userSex;
	private String userAddress;
	public Integer getUserId() {
		return userId;
	}
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public Date getUserBirthday() {
		return userBirthday;
	}
	public void setUserBirthday(Date userBirthday) {
		this.userBirthday = userBirthday;
}
	public String getUserSex() {
		return userSex;
	}
	public void setUserSex(String userSex) {
		this.userSex = userSex;
	}
	public String getUserAddress() {
		return userAddress;
	}
	public void setUserAddress(String userAddress) {
		this.userAddress = userAddress;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
	+ userBirthday + ", userSex="
	+ userSex + ", userAddress=" + userAddress + "]";
	}
}

Dao 接口

/**
* 查询所有用户
* @return
*/
List<User> findAll();

映射配置

<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
	select * from user
</select>

测试查询结果

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

在这里插入图片描述
修改映射配置

使用别名查询
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
	select id as userId,username as userName,birthday as userBirthday,
	sex as userSex,address as userAddress from user
</select>
运行结果:

在这里插入图片描述

resultMap 结果类型

resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。

在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

定义 resultMap

<!-- 建立 User 实体和数据库表的对应关系
	type 属性:指定实体类的全限定类名
	id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
	-->
	<resultMap type="com.itheima.domain.User" id="userMap">
		<id column="id" property="userId"/>
		<result column="username" property="userName"/>
		<result column="sex" property="userSex"/>
		<result column="address" property="userAddress"/>
		<result column="birthday" property="userBirthday"/>
	</resultMap>
	id 标签:用于指定主键字段
	result 标签:用于指定非主键字段
	column 属性:用于指定数据库列名
	property 属性:用于指定实体类属性名称

映射配置

<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
	select * from user
</select>

测试结果

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

运行结果:
在这里插入图片描述

直接使用别名的速度更快,因为是直接修改sql语句,但是麻烦,可重复利用度不高,因此,使用resultMap可将CRUD中的resultType全换成resultMap ,即可一次性解决全部名称不统一带来的问题。当然,resultMap 由于多了一段xml,因此需要多花时间解析,即执行速度没有resultType快。

Mybatis 传统 DAO 层开发[了解]

使用 Mybatis 开发 Dao,通常有两个方法,即原始 Dao 开发方式和 Mapper 接口代理开发方式。 而现在主流的开发方式是接口代理开发方式,这种方式总体上更加简便。 我们的课程讲解也主要以接口代理开发方式为主。 在第二章节已经给大家介绍了基于代理方式的 dao 开发,现在给大家介绍一下基于传统编写 Dao 实现类的开发方式。

Mybatis 实现 DAO 的传统开发方式

持久层 Dao 接口

/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的业务层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
/**
* 保存用户
* @param user
* @return 影响数据库记录的行数
*/
int saveUser(User user);
/**
* 更新用户
* @param user
* * @return 影响数据库记录的行数
*/
int updateUser(User user);
/**
* 根据 id 删除用户
* @param userId
* @return
*/
int deleteUser(Integer userId);
/**
* 查询总记录条数
* @return
*/
int findTotal();
}

持久层 Dao 实现类

/**
*
* <p>Title: UserDaoImpl</p>
* <p>Description: dao 的实现类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class UserDaoImpl implements IUserDao {
	private SqlSessionFactory factory;
	public UserDaoImpl(SqlSessionFactory factory) {
		this.factory = factory;
	}
	@Override
	public List<User> findAll() {
		SqlSession session = factory.openSession();
		List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll");
		session.close();
		return users;
	}
	@Override
	public User findById(Integer userId) {
		SqlSession session = factory.openSession();
		User user = session.selectOne("com.itheima.dao.IUserDao.findById",userId);
		session.close();
		return user;
	}
	@Override
	public int saveUser(User user) {
		SqlSession session = factory.openSession();
		int res = session.insert("com.itheima.dao.IUserDao.saveUser",user);
		session.commit();
		session.close();
		return res;
	}
	@Override
	public int updateUser(User user) {
		SqlSession session = factory.openSession();
		int res = session.update("com.itheima.dao.IUserDao.updateUser",user);
		session.commit();
		session.close();
		return res;
	}
	@Override
	public int deleteUser(Integer userId) {
		SqlSession session = factory.openSession();
		int res = session.delete("com.itheima.dao.IUserDao.deleteUser",userId);
		session.commit();
		session.close();
		return res;
	}
	@Override
	public int findTotal() {
		SqlSession session = factory.openSession();
		int res = session.selectOne("com.itheima.dao.IUserDao.findTotal");
		session.close();
		return res;
	}
}

持久层映射配置

<?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.itheima.dao.IUserDao">
	<!-- 配置查询所有操作 -->
	<select id="findAll" resultType="com.itheima.domain.User">
	select * from user
	</select>
	<!-- 根据 id 查询 -->
	<select id="findById" resultType="com.itheima.domain.User"
	parameterType="int">
		select * from user where id = #{uid}
	</select>
	<!-- 保存用户
	ognl 表达式:它是 apache 提供的一种表达式语言,在 struts2 中也有应用。
	Object Graphic Navigation Language 对象图导航语言
	它是按照一定的语法格式来获取数据的。
	语法格式就是使用 #{对象.对象}的方式
	#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并把值取
	出来
	-->
	<insert id="saveUser" parameterType="com.itheima.domain.User">
		<!-- 配置保存时获取插入的 id -->
		<selectKey keyColumn="id" keyProperty="id" resultType="int">
			select last_insert_id();
		</selectKey>
		insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
	</insert>
	<!-- 更新用户 -->
	<update id="updateUser" parameterType="com.itheima.domain.User">
		update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
	</update>
	<!-- 删除用户 -->
	<delete id="deleteUser" parameterType="java.lang.Integer">
		delete from user where id = #{uid}
	</delete>
	<!-- 查询总记录条数 -->
	<select id="findTotal" resultType="int">
		select count(*) from user;
	</select>
</mapper>

测试类

/**
*
* <p>Title: MybastisCRUDTest</p>
* <p>Description: 测试 mybatis 的 crud 操作</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class MybastisCRUDTest {
	private InputStream in ;
	private SqlSessionFactory factory;
	private IUserDao userDao;
	@Test
	public void testFindAll() {
		List<User> users = userDao.findAll();
		for(User user : users) {
		System.out.println(user);
		}
	}
	@Test
	public void testFindOne() {
		//6.执行操作
		User user = userDao.findById(56);
		System.out.println(user);
	}
	@Test
	public void testSaveUser() throws Exception {
		User user = new User();
		user.setUsername("mybatis dao user");
		//6.执行操作
		int res = userDao.saveUser(user);
		System.out.println(res);
		System.out.println(user.getId());
	}
	@Test
	public void testUpdateUser()throws Exception{
		//1.根据 id 查询
		User user = userDao.findById(41);
		//2.更新操作
		user.setAddress("北京市顺义区");
		int res = userDao.updateUser(user);
		System.out.println(res);
	}
	@Test
	public void testDeleteUser() throws Exception {
		//6.执行操作
		int res = userDao.deleteUser(56);
		System.out.println(res);
	}
	@Test
	public void testFindTotal() throws Exception {
		//6.执行操作
		int res = userDao.findTotal();
		System.out.println(res);
	}
	@Before//在测试方法执行之前执行
	public void init()throws Exception {
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建构建者对象
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		//3.创建 SqlSession 工厂对象
		factory = builder.build(in);
		//4.创建 Dao 接口的实现类
		userDao = new UserDaoImpl(factory);
	}
	@After//在测试方法执行完成之后执行
	public void destroy() throws Exception{
		//7.释放资源
		in.close();
	}
}

非常重要的一张图-分析编写dao实现类Mybatis的执行过程
在这里插入图片描述
可见,不论是增删改查,其实最后用的都是update来执行。

非常重要的一张图-分析代理dao的执行过程
在这里插入图片描述

在这里插入图片描述

SqlMapConfig.xml配置文件

properties(属性)

在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。

第一种
<properties>
	<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
	<property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
	<property name="jdbc.username" value="root"/>
	<property name="jdbc.password" value="1234"/>
</properties>

此时我们的 dataSource 标签就变成了引用上面的配置(key)

<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>
第二种

在 classpath 下定义 db.properties 文件,即将上面的properties属性放在外部文件里。

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234

properties 标签配置

<!-- 配置连接数据库的信息
	resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下(如resource目录下)
	resource="jdbcConfig.properties"
	url 属性:
		URL: Uniform Resource Locator 统一资源定位符
			http://localhost:8080/mystroe/CategoryServlet URL
			协议     主机     端口      URI
		URI: Uniform Resource Identifier 统一资源标识符
			/mystroe/CategoryServlet
		它是可以在 web 应用中唯一定位一个资源的路径
-->
<!--<properties resource="jdbcConfig.properties"></properties>-->
<properties url=
file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties">
</properties>

此时我们的 dataSource 标签就变成了引用上面的配置(key)

<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>

小结:
resource 属性要求配置文件必须在类路径下(如resource目录下);
url 属性不求配置文件的位置,只要按规则写完整路径即可

typeAliases(类型别名)

在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。

在 SqlMapConfig.xml 中配置:
在这里插入图片描述

mappers(映射器)

  • <mapper resource=" " />
使用相对于类路径的资源
如: <mapper resource="com/itheima/dao/IUserDao.xml" />
  • <mapper class=" " />
使用 mapper 接口类路径
如: <mapper class="com.itheima.dao.UserDao"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
  • <package name=""/>
注册指定包下的所有 mapper 接口
如: <package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

Mybatis 连接池与事务深入

连接池:我们在实际开发中都会使用连接池。因为它可以减少我们获取连接所消耗的时间。

在这里插入图片描述

Mybatis 的连接池技术

在这里插入图片描述
在这里插入图片描述

相应地, MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,PooledDataSource 类来表示 UNPOOLED、 POOLED 类型的数据源。

在这里插入图片描述
在这三种数据源中,我们一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术) 。

POOLED和UNPOOLED执行日志对比:
在这里插入图片描述
POOLED和UNPOOLED获取连接工作原理源码对比分析:

UNPOOLED:只是单纯的注册驱动、获取连接、返回连接,没有池的概念

在这里插入图片描述
POOLED:

在这里插入图片描述
在这里插入图片描述

Mybatis 中数据源的配置
我们的数据源配置就是在 SqlMapConfig.xml 文件中, 具体配置如下:
<!-- 配置数据源(连接池)信息 -->
<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>
MyBatis 在初始化时, 根据<dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即:
type=”POOLED”: MyBatis 会创建 PooledDataSource 实例
type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例
type=”JNDI”: MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用
Mybatis 中 DataSource 的存取

MyBatis 是 通 过 工 厂 模 式 来 创 建 数 据 源 DataSource 对 象 的 , MyBatis 定 义 了 抽 象 的 工 厂 接口:org.apache.ibatis.datasource.DataSourceFactory,通过其 getDataSource()方法返回数据源
DataSource。

下面是 DataSourceFactory 源码,具体如下:

package org.apache.ibatis.datasource;
import java.util.Properties;
import javax.sql.DataSource;
/**
* @author Clinton Begin
*/
public interface DataSourceFactory {
	void setProperties(Properties props);
	DataSource getDataSource();
}

MyBatis 创建了 DataSource 实例后,会将其放到 Configuration 对象内的 Environment 对象中, 供
以后使用。

具体分析过程如下:

1.先进入 XMLConfigBuilder 类中,可以找到如下代码:
在这里插入图片描述

2.分析 configuration 对象的 environment 属性,结果如下:
在这里插入图片描述

Mybatis 中连接的获取过程分析

当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis 才会去调用 dataSource 对象来创建 java.sql.Connection对象。也就是说, java.sql.Connection对象的创建一直延迟到执行 SQL语句的时候。

@Test
public void testSql() throws Exception {
	InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
	SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
	SqlSession sqlSession = factory.openSession();
	List<User> list = sqlSession.selectList("findUserById",41);
	System.out.println(list.size());
}

只有当第 4 句 sqlSession.selectList(“findUserById”),才会触发 MyBatis 在底层执行下面这个方法来创建 java.sql.Connection 对象。

如何证明它的加载过程呢?

我们可以通过断点调试,在 PooledDataSource 中找到如下 popConnection()方法,如下所示:
在这里插入图片描述
分析源代码,得出 PooledDataSource 工作原理如下:

在这里插入图片描述
下面是连接获取的源代码:
在这里插入图片描述
最后我们可以发现,真正连接打开的时间点,只是在我们执行SQL语句时,才会进行。其实这样做我们也可以进一步发现,数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中。

Mybatis 的事务控制

什么是事务
事务的四大特性ACID
不考虑隔离性会产生的三个问题
解决办法:四种隔离级别

JDBC 中事务的回顾

在 JDBC 中我们可以通过手动方式将事务的提交改为自动方式,通过 setAutoCommit()方法就可以调整。通过 JDK 文档,我们找到该方法如下:
在这里插入图片描述

那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的setAutoCommit()方法来设置事务提交方式的。

Mybatis 中事务提交方式

Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。
我们运行之前所写的代码:
@Test
public void testSaveUser() throws Exception {
	User user = new User();
	user.setUsername("mybatis user09");
	//6.执行操作
	int res = userDao.saveUser(user);
	System.out.println(res);
	System.out.println(user.getId());
}
@Before//在测试方法执行之前执行
public void init()throws Exception {
	//1.读取配置文件
	in = Resources.getResourceAsStream("SqlMapConfig.xml");
	//2.创建构建者对象
	SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
	//3.创建 SqlSession 工厂对象
	factory = builder.build(in);
	//4.创建 SqlSession 对象
	session = factory.openSession();
	//5.创建 Dao 的代理对象
	userDao = session.getMapper(IUserDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
	//7.提交事务
	session.commit();
	//8.释放资源
	session.close();
	in.close();
}

在这里插入图片描述

我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false 再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

Mybatis映射文件的SQL深入

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

动态 SQL 之if标签

我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

持久层 Dao 接口

/**
* 根据用户信息,查询用户列表
* @param user
* @return
*/
List<User> findByUser(User user);

持久层 Dao 映射配置

<select id="findByUser" resultType="user" parameterType="user">
	select * from user where 1=1
	<if test="username!=null and username != '' ">
		and username like #{username}
	</if>
	<if test="address != null">
		and address like #{address}
	</if>
</select>
注意: <if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
另外要注意 where 1=1 的作用~

测试

@Test
public void testFindByUser() {
	User u = new User();
	u.setUsername("%王%");
	u.setAddress("%顺义%");
	//6.执行操作
	List<User> users = userDao.findByUser(u);
	for(User user : users) {
		System.out.println(user);
	}
}

动态 SQL 之where标签

为了简化上面 where 1=1 的条件拼装,我们可以采用<where>标签来简化开发。

持久层 Dao 映射配置

<!-- 根据用户信息查询 -->
<select id="findByUser" resultType="user" parameterType="user">
	<include refid="defaultSql"></include>
	<where>
		<if test="username!=null and username != '' ">
			and username like #{username}
		</if>
		<if test="address != null">
			and address like #{address}
		</if>
	</where>
</select>

动态标签之foreach标签

需求

传入多个 id 查询用户信息,用下边两个 sql 实现:

SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。
这样我们将如何进行参数的传递?

在 QueryVo 中加入一个 List 集合用于封装参数

/**
*
* <p>Title: QueryVo</p>
* <p>Description: 查询的条件</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class QueryVo implements Serializable {
	private List<Integer> ids;
	public List<Integer> getIds() {
		return ids;
	}
	public void setIds(List<Integer> ids) {
		this.ids = ids;
	}
}
	

持久层 Dao 接口

/**
* 根据 id 集合查询用户
* @param vo
* @return
*/
List<User> findInIds(QueryVo vo);

持久层 Dao 映射配置

<!--抽取SQL 片段 -->
<sql id="defaultSql">
	select * from user
</sql>
<!-- 查询所有用户在 id 的集合之中 -->
<select id="findInIds" resultType="user" parameterType="queryvo">
	<!-- select * from user where id in (1,2,3,4,5); -->
	<include refid="defaultSql"></include>
	<where>
		<if test="ids != null and ids.size() > 0">
		<foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
			#{uid}
		</foreach>
		</if>
	</where>
</select>

SQL 语句:
	select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
	collection:代表要遍历的集合元素,注意编写时不要写#{}
	open:代表语句的开始部分
	close:代表结束部分
	item:代表遍历集合的每个元素,生成的变量名
	sperator:代表分隔符

编写测试方法

@Test
public void testFindInIds() {
	QueryVo vo = new QueryVo();
	List<Integer> ids = new ArrayList<Integer>();
	ids.add(41);
	ids.add(42);
	ids.add(43);
	ids.add(46);
	ids.add(57);
	vo.setIds(ids);
	//6.执行操作
	List<User> users = userDao.findInIds(vo);
	for(User user : users) {
		System.out.println(user);
	}
}

Mybatis 中简化编写的 SQL 片段

Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。

定义代码片段

<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
	select * from user
</sql>

引用代码片段

<!-- 配置查询所有操作 -->
<select id="findAll" resultType="user">
	<include refid="defaultSql"></include>
</select>

<!-- 根据 id 查询 -->
<select id="findById" resultType="User" parameterType="int">
	<include refid="defaultSql"></include>
	where id = #{uid}
</select>

Mybatis 多表查询之一对多

mybatis中的多表查询:
		示例:用户和账户
			一个用户可以有多个账户
			一个账户只能属于一个用户(多个账户也可以属于同一个用户)
		步骤:
			1、建立两张表:用户表,账户表
				让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
			2、建立两个实体类:用户实体类和账户实体类
				让用户和账户的实体类能体现出来一对多的关系
			3、建立两个配置文件
				用户的配置文件
				账户的配置文件
			4、实现配置:
				当我们查询用户时,可以同时得到用户下所包含的账户信息
				当我们查询账户时,可以同时得到账户的所属用户信息

一对一查询(多对一)

需求

查询所有账户信息,关联查询下单用户信息。

注意:

因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

方式一

定义账户信息的实体类

/**
*
* <p>Title: Account</p>
* <p>Description: 账户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class Account implements Serializable {
	private Integer id;
	private Integer uid;
	private Double money;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public Integer getUid() {
		return uid;
	}
	public void setUid(Integer uid) {
		this.uid = uid;
	}
	public Double getMoney() {
		return money;
	}
	public void setMoney(Double money) {
		this.money = money;
	}
	@Override
	public String toString() {
		return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]";
	}
}

编写 Sql 语句

实现查询账户信息时,也要查询账户所对应的用户信息。

select a.*,u.username,u.address from account a, user u where u.id = a.uid;

在这里插入图片描述
定义 AccountUser 类

为了能够封装上面 SQL 语句的查询结果,定义 AccountCustomer 类中要包含账户信息同时还要包含用户信息,所以我们要在定义 AccountUser 类时可以继承 User 类。

/**
*
* <p>Title: AccountUser</p>
* <p>Description: 它是 account 的子类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class AccountUser extends Account implements Serializable {
	private String username;
	private String address;
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	@Override
	public String toString() {
		return super.toString() + " AccountUser [username=" + username + ",
		address=" + address + "]";
	}
}

定义账户的持久层 Dao 接口

public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
	List<AccountUser> findAll();
}

定义 IAccountDao.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">
<mapper namespace="com.itheima.dao.IAccountDao">
	<!-- 配置查询所有操作-->
	<select id="findAll" resultType="accountuser">
		select a.*,u.username,u.address from account a,user u where a.uid =u.id;
	</select>
</mapper>

注意:因为上面查询的结果中包含了账户信息同时还包含了用户信息,所以我们的返回值类型 returnType的值设置为 AccountUser 类型,这样就可以接收账户信息和用户信息了。

创建 AccountTest 测试类

/**
*
* <p>Title: MybastisCRUDTest</p>
* <p>Description: 一对多账户的操作</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class AccountTest {
	private InputStream in ;
	private SqlSessionFactory factory;
	private SqlSession session;
	private IAccountDao accountDao;
	@Test
	public void testFindAll() {
		//6.执行操作
		List<AccountUser> accountusers = accountDao.findAll();
		for(AccountUser au : accountusers) {
		System.out.println(au);
		}
	}
	@Before//在测试方法执行之前执行
	public void init()throws Exception {
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建构建者对象
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		//3.创建 SqlSession 工厂对象
		factory = builder.build(in);
		//4.创建 SqlSession 对象
		session = factory.openSession();
		//5.创建 Dao 的代理对象
		accountDao = session.getMapper(IAccountDao.class);
	}
	@After//在测试方法执行完成之后执行
	public void destroy() throws Exception{
		session.commit();
		//7.释放资源
		session.close();
		in.close();
	}
}

小结:定义专门的 po 类作为输出类型,其中定义了 sql 查询结果集所有的字段。此方法较为简单,企业中使用普遍

方式二

使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。

通过面向对象的(has a)关系可以得知,我们可以在 Account 类中加入一个 User 类的对象来代表这个账户是哪个用户的。

修改 Account 类

在 Account 类中加入 User 类的对象作为 Account 类的一个属性。

/**
*
* <p>Title: Account</p>
* <p>Description: 账户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class Account implements Serializable {
	private Integer id;
	private Integer uid;
	private Double money;
	private User user;
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public Integer getUid() {
		return uid;
	}
	public void setUid(Integer uid) {
		this.uid = uid;
	}
	public Double getMoney() {
		return money;
	}
	public void setMoney(Double money) {
		this.money = money;
	}
	@Override
	public String toString() {
		return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]";
	}
}

修改 AccountDao 接口中的方法

/**
*
* <p>Title: IAccountDao</p>
* <p>Description: 账户的持久层接口</p>
* * <p>Company: http://www.itheima.com/ </p>
*/
public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
	List<Account> findAll();
}
注意:第二种方式,将返回值改 为了 Account 类型。
因为 Account 类中包含了一个 User 类的对象,它可以封装账户所对应的用户信息。

重新定义 AccountDao.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指的是哪个方法的接口,id指的是哪个方法,resultType指的是封装到哪里去-->
<mapper namespace="com.itheima.dao.IAccountDao">
<!--定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="account">
    <id property="id" column="aid"></id>
    <result property="uid" column="uid"></result>
    <result property="money" column="money"></result>
<!--一对一的关系映射:配置封装User的内容 (JavaType是用来指定pojo中属性的类型)-->
    <association property="user" javaType="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
    </association>
</resultMap>
<!--查询所有账户,同时还要获取当前账户所属的用户信息-->
    <select id="findAll" resultMap="accountUserMap">
        select u.*,a.id as aid,a.uid,a.money from account a, user u where u.id = a.uid;
    </select>
</mapper>

在 AccountTest 类中加入测试方法

@Test
public void testFindAll() {
	List<Account> accounts = accountDao.findAll();
	for(Account au : accounts) {
		System.out.println(au);
		System.out.println(au.getUser());
	}
}

一对多查询

需求:

查询所有用户信息及用户关联的账户信息。

分析:

用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。

编写 SQL 语句

 select * from user u left outer join account a on u.id = a.uid;

在这里插入图片描述
User 类加入 List<Account>

package com.itheima.domain;


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

public class User implements Serializable {
    private Integer id;
    private  String username;
    private  String address;
    private String sex;
    private Date birthday;
    //一对多关系映射:主表实体应该包含从表实体的集合引用
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

用户持久层 Dao 接口中加入查询方法

/**
* 查询所有用户,同时获取出每个用户下的所有账户信息
* @return
*/
List<User> findAll();

用户持久层 Dao 映射文件配置

<?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指的是哪个方法的接口,id指的是哪个方法,resultType指的是封装到哪里去-->
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 定义user的resultMap-->
    <resultMap id="userAccountMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
<!--配置user对象中accounts集合的映射-->
        <collection property="accounts" ofType="account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>
<!--查询所有-->
    <select id="findAll" resultMap="userAccountMap">
        select * from user u left outer join account a on u.id = a.uid;
    </select>
</mapper>

collection
部分定义了用户关联的账户信息。表示关联查询结果集
property="accounts":
关联查询的结果集存储在 User 对象的上哪个属性。
ofType="account":
指定关联查询的结果集中的对象类型即 List中的对象类型。此处可以使用别名,也可以使用全限定名。

测试方法

package com.itheima.test;

import com.itheima.dao.IAccountDao;
import com.itheima.dao.IUserDao;
import com.itheima.domain.Account;
import com.itheima.domain.AccountUser;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }
    @After//用于在测试方法执行之后执行
    public void destroy() throws IOException {
        //提交事务(避免发生回滚)
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }
    // 测试查询所有
    @Test
    public void testFindAll(){
        //5.执行查询所有方法
       List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
    }
}

Mybatis 多表查询之多对多

在这里插入图片描述
多对多查询实质上就是双向的一对多。
在这里插入图片描述
实现 Role 到 User 多对多

用户与角色的关系模型
在这里插入图片描述
在这里插入图片描述
业务要求及实现 SQL

需求:

实现查询所有对象并且加载它所分配的用户信息。

分析:

查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。

下面是实现的 SQL 语句:

        select u.*,r.id as rid,r.role_name,r.role_desc from role r
        left outer join user_role ur on r.id = ur.rid
        left outer join user u on u.id = ur.uid

编写角色实体类

package com.itheima.domain;

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

public class Role implements Serializable {
    private Integer roleId;
    private String roleName;
    private String roleDesc;

    //多对多的关系映射,一个角色可以赋给多个用户
    private List<User> users;

    public List<User> getUsers() {
        return users;
    }

    public void setUers(List<User> users) {
        this.users = users;
    }

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

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

    public String getRoleDesc() {
        return roleDesc;
    }

    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }
}

编写 Role 持久层接口

package com.itheima.dao;

import com.itheima.domain.Role;

import java.util.List;

public interface IRoleDao {
//    查询所有角色
    List<Role> findAll();
}

编写映射文件

<?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.itheima.dao.IRoleDao">
<!--定义role表的resultMap-->
    <resultMap id="roleMap" type="role">
        <id property="roleId" column="rid"></id>
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
        <collection property="users" ofType="user">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
        </collection>
    </resultMap>
<!--查询所有-->
    <select id="findAll" resultMap="roleMap">
        select u.*,r.id as rid,r.role_name,r.role_desc from role r
        left outer join user_role ur on r.id = ur.rid
        left outer join user u on u.id = ur.uid
    </select>
</mapper>

编写测试类

package com.itheima.test;

import com.itheima.dao.IRoleDao;
import com.itheima.dao.IUserDao;
import com.itheima.domain.Role;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class RoleTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IRoleDao roleDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
        roleDao = sqlSession.getMapper(IRoleDao.class);
    }
    @After//用于在测试方法执行之后执行
    public void destroy() throws IOException {
        //提交事务(避免发生回滚)
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }
    // 测试查询所有
    @Test
    public void testFindAll(){
        //5.执行查询所有方法
       List<Role> roles = roleDao.findAll();
        for (Role role : roles) {
            System.out.println("---------打印每个角色的信息----------");
            System.out.println(role);
            System.out.println(role.getUsers());
        }

    }

}

实现 User 到 Role 的多对多

从 User 出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样我们就可以认为 User 与 Role 的多对多关系,可以被拆解成两个一对多关系来实现。

编写用户实体类

package com.itheima.domain;


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

public class User implements Serializable {
    private Integer id;
    private  String username;
    private  String address;
    private String sex;
    private Date birthday;
    //多对多的关系映射,一个用户可以具备多个角色
    private List<Role> roles;

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

编写 User 持久层接口

package com.itheima.dao;

import com.itheima.domain.User;

import java.util.List;

//用户的持久层接口
public interface IUserDao {
    //查询所有
    List<User> findAll();

    //通过id查询用户
    User findById(Integer userId);

}

编写映射文件

<?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指的是哪个方法的接口,id指的是哪个方法,resultType指的是封装到哪里去-->
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 定义user的resultMap-->
    <!-- 定义User的resultMap-->
    <resultMap id="userMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <!-- 配置角色集合的映射 -->
        <collection property="roles" ofType="role">
            <id property="roleId" column="rid"></id>
            <result property="roleName" column="role_name"></result>
            <result property="roleDesc" column="role_desc"></result>
        </collection>
    </resultMap>
<!--查询所有-->
    <select id="findAll" resultMap="userMap">
        select u.*,r.id as rid,r.role_name,r.role_desc from user u
         left outer join user_role ur on u.id = ur.uid
         left outer join role r on r.id = ur.rid
    </select>



</mapper>

编写测试类

package com.itheima.test;

import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }
    @After//用于在测试方法执行之后执行
    public void destroy() throws IOException {
        //提交事务(避免发生回滚)
        sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }
    // 测试查询所有
    @Test
    public void testFindAll(){
        //5.执行查询所有方法
       List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println("-------每个用户的信息-----------");
            System.out.println(user);
            System.out.println(user.getRoles());

        }
    }
}

小结:
多对一站在“多”的角度可以看作一对一,二者互相关联(<association property="user" javaType="user">
一对多是站在“一”的角度看多的“集合”( <collection property="roles" ofType="role">
多对多则是双向的一对多

Mybatis 延迟加载策略

### 延迟加载

问题:在一对多中,当我们有一个用户,它有100个账户。

在查询用户的时候,要不要把关联的账户查出来?
在查询账户的时候,要不要把关联的用户查出来?
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。
在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。

什么是延迟加载

在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)

什么是立即加载

不管用不用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多

	一对多,多对多:通常情况下我们都是采用延迟加载。
	多对一,一对一:通常情况下我们都是采用立即加载。

mybatis实现多表操作时,我们使用了 resultMap来实现一对一,一对多,多对多关系的操作。主要是通过 association、 collection 实现一对一及一对多映射。 association、 collection 具备延迟加载功
能。

使用 assocation 实现延迟加载

需求:查询账户信息同时查询用户信息。

账户的持久层 DAO 接口

public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<Account> findAll();
}

账户的持久层映射文件

<?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.itheima.dao.IAccountDao">
	<!-- 建立对应关系 -->
	<resultMap type="account" id="accountMap">
		<id column="aid" property="id"/>
		<result column="uid" property="uid"/>
		<result column="money" property="money"/>
		<!-- 它是用于指定从表方的引用实体属性的 -->
		<association property="user" javaType="user"
			select="com.itheima.dao.IUserDao.findById"
			column="uid">
		</association>
	</resultMap>
	<select id="findAll" resultMap="accountMap">
		select * from account
	</select>
</mapper>
select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数

用户的持久层接口和映射文件

public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}


<?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.itheima.dao.IUserDao">
	<!-- 根据 id 查询 -->
	<select id="findById" resultType="user" parameterType="int" >
		select * from user where id = #{uid}
	</select>
</mapper>

开启 Mybatis 的延迟加载策略
在这里插入图片描述
在这里插入图片描述

<settings>
	<setting name="lazyLoadingEnabled" value="true"/>
	<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<?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-->
    <properties resource="jdbcConfig.properties"></properties>
    <!--使用延时加载,其中aggressiveLazyLoading属性3.4.1后默认为false,我们使用的是3.4.5,因此可省略-->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    <!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
    <typeAliases>
        <package name="com.itheima.domain"></package>
    </typeAliases>

    <!--配置环境-->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务 -->
            <transactionManager type="JDBC"></transactionManager>

            <!--配置连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置映射文件的位置 -->
    <mappers>
        <package name="com.itheima.dao"></package>
    </mappers>
</configuration>

编写测试只查账户信息不查用户信息

public class AccountTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@Test
public void testFindAll() {
	//6.执行操作
	List<Account> accounts = accountDao.findAll();
	}
	@Before//在测试方法执行之前执行
	public void init()throws Exception {
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建构建者对象
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		//3.创建 SqlSession 工厂对象
		factory = builder.build(in);
		//4.创建 SqlSession 对象
		session = factory.openSession();
		//5.创建 Dao 的代理对象
		accountDao = session.getMapper(IAccountDao.class);
	}
	@After//在测试方法执行完成之后执行
	public void destroy() throws Exception{
		//7.释放资源
		session.close();
		in.close();
	}
}

在这里插入图片描述

使用 Collection 实现延迟加载

同样我们也可以在一对多关系配置的结点中配置延迟加载策略。
<collection>结点中也有 select 属性, column 属性。

需求:完成加载用户对象时,查询该用户所拥有的账户信息。

在 User 实体类中加入 List<Account>属性

public class User implements Serializable {
	private Integer id;
	private String username;
	private Date birthday;
	private String sex;
	private String address;
	private List<Account> accounts;
	public List<Account> getAccounts() {
		return accounts;
	}
	public void setAccounts(List<Account> accounts) {
		this.accounts = accounts;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public Date getBirthday() {
		return birthday;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", birthday=" + birthday
		+ ", sex=" + sex + ", address="
		+ address + "]";
	}
}

编写用户和账户持久层接口的方法

/**
* 查询所有用户,同时获取出每个用户下的所有账户信息
* @return
*/
List<User> findAll();
/**
* 根据用户 id 查询账户信息
* @param uid
* @return
*/
List<Account> findByUid(Integer uid);

编写用户持久层映射配置

<resultMap type="user" id="userMap">
	<id column="id" property="id"></id>
	<result column="username" property="username"/>
	<result column="address" property="address"/>
	<result column="sex" property="sex"/>
	<result column="birthday" property="birthday"/>
	<!-- collection 是用于建立一对多中集合属性的对应关系
	ofType 用于指定集合元素的数据类型
	select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
	column 是用于指定使用哪个字段的值作为条件查询
	-->
	<collection property="accounts" ofType="account"
		select="com.itheima.dao.IAccountDao.findByUid"
		column="id">
	</collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
	select * from user
</select>

<collection>标签:
主要用于加载关联的集合对象
select 属性:
用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一
个字段名了

编写账户持久层映射配置

<!-- 根据用户 id 查询账户信息 -->
<select id="findByUid" resultType="account" parameterType="int">
	select * from account where uid = #{uid}
</select>

测试只加载用户信息

public class UserTest {
	private InputStream in ;
	private SqlSessionFactory factory;
	private SqlSession session;
	private IUserDao userDao;
	@Test
	public void testFindAll() {
		//6.执行操作
		List<User> users = userDao.findAll();
	}
	@Before//在测试方法执行之前执行
	public void init()throws Exception {
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建构建者对象
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		//3.创建 SqlSession 工厂对象
		factory = builder.build(in);
		//4.创建 SqlSession 对象
		session = factory.openSession();
		//5.创建 Dao 的代理对象
		userDao = session.getMapper(IUserDao.class);
	}
	@After//在测试方法执行完成之后执行
	public void destroy() throws Exception{
		session.commit();
		//7.释放资源
		session.close();
		in.close();
	}
}

在这里插入图片描述

小结

延迟加载的思想即为在用的时候去调用对象配置文件里的sql语句来实现功能。

关键:
(1)主配置文件里的配置

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

(2)如何进行配置
一对一:使用 assocation 实现延迟加载
一对多:使用 Collection 实现延迟加载

mybatis中的缓存

在这里插入图片描述
在这里插入图片描述

一级缓存:

它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。

IUserDao.xml映射文件如下:
在这里插入图片描述
测试:
在这里插入图片描述
在这里插入图片描述
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除, commit(), close()等方法时,就会清空一级缓存。

Mybatis 二级缓存

在这里插入图片描述

在这里插入图片描述
如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交, 将会清空该 mapper 映射下的二级缓存区域的数据。

sqlSession2 去查询与 sqlSession1 相同的用户信息, 首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

但是,注意:二级缓存中存放的是如图所示的散列数据,当从中拿对象时,会将这些数据封装为不同的对象。因此,读相同的数据可以从从二级缓存中读出,但是拿到的对象是不一样的。

二级缓存的开启与关闭
  • 第一步:在 SqlMapConfig.xml 文件开启二级缓存
<settings>
	<!-- 开启二级缓存的支持 -->
	<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
  • 第二步:配置相关的 Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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.itheima.dao.IUserDao">
	<!-- 开启二级缓存的支持 -->
	<cache></cache>
</mapper>
  • 第三步: 配置 statement 上面的 useCache 属性
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
	select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意: 针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

注意:当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

Mybatis 注解开发

Mybatis 注解开发指的是 Mapper 映射文件

这几年来注解开发越来越流行, Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。 本次我们先围绕一些基本的 CRUD 来学习,再学习复杂映射关系及延迟加载。
在这里插入图片描述

mybatis 的常用注解说明

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用

@SelectKey在Mybatis中是为了解决Insert数据时不支持主键自动生成的问题,他可以很随意的设置生成主键的方式。
statement是要运行的SQL语句,它的返回值通过resultType来指定
before表示查询语句statement运行的时机
keyProperty表示查询结果赋值给代码中的哪个对象,keyColumn表示将查询结果赋值给数据库表中哪一列
keyProperty和keyColumn都不是必需的,有没有都可以
before=true,插入之前进行查询,可以将查询结果赋给keyProperty和keyColumn,赋给keyColumn相当于更改数据库
befaore=false,先插入,再查询,这时只能将结果赋给keyProperty
赋值给keyProperty用来“读”数据库,赋值给keyColumn用来写数据库
selectKey的两大作用:1、生成主键;2、获取刚刚插入数据的主键。
使用selectKey,并且使用MySQL的last_insert_id()函数时,before必为false,也就是说必须先插入然后执行last_insert_id()才能获得刚刚插入数据的ID

使用 Mybatis 注解实现基本 CRUD

单表的 CRUD 操作是最基本的操作,前面我们的学习都是基于 Mybaits 的映射文件来实现的。

编写实体类

public class User implements Serializable {
	private Integer userId;
	private String userName;
	private Date userBirthday;
	private String userSex;
	private String userAddress;
	public Integer getUserId() {
		return userId;
	}
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public Date getUserBirthday() {
		return userBirthday;
	}
	public void setUserBirthday(Date userBirthday) {
		this.userBirthday = userBirthday;
	}
	public String getUserSex() {
		return userSex;
	}
	public void setUserSex(String userSex) {
		this.userSex = userSex;
	}
	public String getUserAddress() {
		return userAddress;
	}
	public void setUserAddress(String userAddress) {
		this.userAddress = userAddress;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
		+ userBirthday + ", userSex="
		+ userSex + ", userAddress=" + userAddress + "]";
	}
}
注意:此处我们故意和数据库表的列名不一致。

使用注解方式开发持久层接口

public interface IUserDao {
	/**
	* 查询所有用户
	* @return
	*/
	@Select("select * from user")
	@Results(id="userMap",
		value= {
			@Result(id=true,column="id",property="userId"),
			@Result(column="username",property="userName"),
			@Result(column="sex",property="userSex"),
			@Result(column="address",property="userAddress"),
			@Result(column="birthday",property="userBirthday")
		})
	List<User> findAll();
	/**
	* 根据 id 查询一个用户
	* @param userId
	* @return
	*/
	@Select("select * from user where id = #{uid} ")
	@ResultMap("userMap")
	User findById(Integer userId);
	/**
	* 保存操作
	* @param user
	* @return
	*/
	@Insert("insert into user(username,sex,birthday,address)values(#{username},#{sex},#{birthday},#{address})")
	@SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" })
	int saveUser(User user);
	/**
	* 更新操作
	* @param user
	* @return
	*/
	@Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where idn =#{id} ")
	int updateUser(User user);
	/**
	* 删除用户
	* @param userId
	* @return
	*/
	@Delete("delete from user where id = #{uid} ")
	int deleteUser(Integer userId);
	/**
	* 查询使用聚合函数
	* @return
	*/
	@Select("select count(*) from user ")
	int findTotal();
	/**
	* 模糊查询
	* @param name
	* @return
	*/
	@Select("select * from user where username like #{username} ")
	List<User> findByName(String name);
}
通过注解方式,我们就不需要再去编写 UserDao.xml 映射文件了。

编写 SqlMapConfig 配置文件

<?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 文件的位置 -->
	<properties resource="jdbcConfig.properties"></properties>
	<!-- 配置别名的注册 -->
	<typeAliases>
	<package name="com.itheima.domain"/>
	</typeAliases>
	<!-- 配置环境 -->
	<environments default="mysql">
		<!-- 配置 mysql 的环境 -->
		<environment id="mysql">
		<!-- 配置事务的类型是 JDBC -->
		<transactionManager type="JDBC"></transactionManager>
		<!-- 配置数据源 -->
			<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>
	<!-- 配置映射信息 -->
	<mappers>
		!-- 配置 dao 接口的位置,它有两种方式
		第一种:使用 mapper 标签配置 class 属性
		第二种:使用 package 标签,直接指定 dao 接口所在的包
		-->
		<package name="com.itheima.dao"/>
	</mappers>
</configuration>

编写测试方法

public class MybatisAnnotationCRUDTest {
	/**
	* 测试查询所有
	*/
	@Test
	public void testFindAll() {
		List<User> users = userDao.findAll();
		for(User user : users) {
		System.out.println(user);
		}
	}
	/**
	* 测试查询一个
	*/
	@Test
	public void testFindById() {
		User user = userDao.findById(41);
		System.out.println(user);
	}
	/**
	* 测试保存
	*/
	@Test
	public void testSave() {
		User user = new User();
		user.setUserName("mybatis annotation");
		user.setUserSex("男");
		user.setUserAddress("北京市顺义区");
		user.setUserBirthday(new Date());
		int res = userDao.saveUser(user);
		System.out.println("影响数据库记录的行数: "+res);
		System.out.println("插入的主键值: "+user.getUserId());
	}
	/**
	* 测试更新
	*/
	@Test
	public void testUpdate() {
		User user = userDao.findById(63);
		user.setUserBirthday(new Date());
		user.setUserSex("女");
		int res = userDao.updateUser(user);
		System.out.println(res);
	}
	/**
	* 测试删除
	*/
	@Test
	public void testDelete() {
		int res = userDao.deleteUser(63);
		System.out.println(res);
	}
	/**
	* 测试查询使用聚合函数
	*/
	@Test
	public void testFindTotal() {
		int res = userDao.findTotal();
		System.out.println(res);
	}
	/**
	* 测试模糊查询
	*/
	@Test
	public void testFindByName() {
		List<User> users = userDao.findByName("%m%");
		for(User user : users) {
			System.out.println(user);
		}
	}
	private InputStream in;
	private SqlSessionFactory factory;
	private SqlSession session;
	private IUserDao userDao;
	@Before//junit 的注解
	public void init()throws Exception{
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建工厂
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		factory = builder.build(in);
		//3.创建 session
		session = factory.openSession();
		//4.创建代理对象
		userDao = session.getMapper(IUserDao.class);
	}
	@After//junit 的注解
	public void destroy()throws Exception {
		//提交事务
		session.commit();
		//释放资源
		session.close();
		//关闭流
		in.close();
	}
}

使用注解实现复杂关系映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现, 在使用注解开发时我们需要借助@Results 注解, @Result 注解, @One 注解, @Many 注解。

@Results 注解

代替的是标签<resultMap>
该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results{@Result(), @Result() })或@Results@Result())


@Resutl 注解

代替了 <id>标签和<result>标签
@Result 中 属性介绍:
	id 是否是主键字段
	column 数据库的列名
	property 需要装配的属性名
	one 需要使用的@One 注解( @Result( one=@One)()))
	many 需要使用的@Many 注解( @Result( many=@many)()))
	
@One 注解(一对一)
	代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
	select 指定用来多表查询的 sqlmapper
	fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式:
	@Result(column=" ",property="",one=@One(select=""))
	
@Many 注解(多对一)
	代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
	注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType
(一般为 ArrayList)但是注解中可以不定义;
使用格式:
	@Result(property="",column="",many=@Many(select=""))
使用注解实现一对一复杂关系映射及延迟加载

需求:加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

添加 User 实体类及 Account 实体类

/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
	private Integer userId;
	private String userName;
	private Date userBirthday;
	private String userSex;
	private String userAddress;
	public Integer getUserId() {
		return userId;
	}
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public Date getUserBirthday() {
		return userBirthday;
	}
	public void setUserBirthday(Date userBirthday) {
		this.userBirthday = userBirthday;
	}
	public String getUserSex() {
		return userSex;
	}
	public void setUserSex(String userSex) {
		this.userSex = userSex;
	}
	public String getUserAddress() {
		return userAddress;
	}
	public void setUserAddress(String userAddress) {
		this.userAddress = userAddress;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
		+ userBirthday + ", userSex="
		+ userSex + ", userAddress=" + userAddress + "]";
	}
}
	/**
	*
	* <p>Title: Account</p>
	* <p>Description: 账户的实体类</p>
	* <p>Company: http://www.itheima.com/ </p>
	*/
	public class Account implements Serializable {
		private Integer id;
		private Integer uid;
		private Double money;
		//多对一关系映射:从表方应该包含一个主表方的对象引用
		private User user;
		public User getUser() {
			return user;
		}
		public void setUser(User user) {
			this.user = user;
		}
		public Integer getId() {
			return id;
		}
		public void setId(Integer id) {
			this.id = id;
		}
		public Integer getUid() {
			return uid;
		}
		public void setUid(Integer uid) {
			this.uid = uid;
		}
		public Double getMoney() {
			return money;
		}
		public void setMoney(Double money) {
			this.money = money;
		}
		@Override
		public String toString() {
		return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]";
		}
}

添加账户的持久层接口并使用注解配置

/**
*
* <p>Title: IAccountDao</p>
* <p>Description: 账户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IAccountDao {
	/**
	* 查询所有账户,采用延迟加载的方式查询账户的所属用户
	* @return
	*/
	@Select("select * from account")
	@Results(id="accountMap",
		value= {
			@Result(id=true,column="id",property="id"),
			@Result(column="uid",property="uid"),
			@Result(column="money",property="money"),
			@Result(column="uid",
				property="user",
				one=@One(select="com.itheima.dao.IUserDao.findById",
					fetchType=FetchType.LAZY)
			)
		})
	List<Account> findAll();
}

添加用户的持久层接口并使用注解配置

/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
	/**
	* 查询所有用户
	* @return
	*/
	@Select("select * from user")
	@Results(id="userMap",
		value= {
			@Result(id=true,column="id",property="userId"),
			@Result(column="username",property="userName"),
			@Result(column="sex",property="userSex"),
			@Result(column="address",property="userAddress"),
			@Result(column="birthday",property="userBirthday")
	})
	List<User> findAll();
	/**
	* 根据 id 查询一个用户
	* @param userId
	* @return
	*/
	@Select("select * from user where id = #{uid} ")
	@ResultMap("userMap")
	User findById(Integer userId);
}

测试一对一关联及延迟加载

/**
*
* <p>Title: AccountTest</p>
* <p>Description: 账户的测试类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class AccountTest {
	@Test
	public void testFindAll() {
	List<Account> accounts = accountDao.findAll();
	// for(Account account : accounts) {
	// System.out.println(account);
	// System.out.println(account.getUser());
	// }
}
使用注解实现一对多复杂关系映射

需求:查询用户信息时,也要查询他的账户列表。使用注解方式实现。

分析:一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。

User 实体类加入 List<Account>

/**
*
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
	private Integer userId;
	private String userName;
	private Date userBirthday;
	private String userSex;
	private String userAddress;
	//一对多关系映射:主表方法应该包含一个从表方的集合引用
	private List<Account> accounts;
	public List<Account> getAccounts() {
		return accounts;
	}
	public void setAccounts(List<Account> accounts) {
		this.accounts = accounts;
	}
	public Integer getUserId() {
		return userId;
	}
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public Date getUserBirthday() {
		return userBirthday;
	}
	public void setUserBirthday(Date userBirthday) {
		this.userBirthday = userBirthday;
	}
	public String getUserSex() {
		return userSex;
	}
	public void setUserSex(String userSex) {
		this.userSex = userSex;
	}
	public String getUserAddress() {
		return userAddress;
	}
	public void setUserAddress(String userAddress) {
		this.userAddress = userAddress;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userName=" + userName + ", userBirthday="
		+ userBirthday + ", userSex="
		+ userSex + ", userAddress=" + userAddress + "]";
	}
}

编写用户的持久层接口并使用注解配置

/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IUserDao {
	/**
	* 查询所有用户
	* @return
	*/
	@Select("select * from user")
	@Results(id="userMap",
		value= {
				@Result(id=true,column="id",property="userId"),
				@Result(column="username",property="userName"),
				@Result(column="sex",property="userSex"),
				@Result(column="address",property="userAddress"),
				@Result(column="birthday",property="userBirthday"),
				@Result(column="id",property="accounts",
					many=@Many(
						select="com.itheima.dao.IAccountDao.findByUid",
						fetchType=FetchType.LAZY
							)
					)
		})
	List<User> findAll();
}
@Many:
相当于<collection>的配置
select 属性:代表将要执行的 sql 语句
fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值

编写账户的持久层接口并使用注解配置

/**
*
* <p>Title: IAccountDao</p>
* <p>Description: 账户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public interface IAccountDao {
	/**
	* 根据用户 id 查询用户下的所有账户
	* * @param userId
	* @return
	*/
	@Select("select * from account where uid = #{uid} ")
	List<Account> findByUid(Integer userId);
}

添加测试方法

/**
*
* <p>Title: MybatisAnnotationCRUDTest</p>
* <p>Description: mybatis 的注解 crud 测试</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class UserTest {
	/**
	* 测试查询所有
	*/
	@Test
	public void testFindAll() {
		List<User> users = userDao.findAll();
		// for(User user : users) {
		// System.out.println("-----每个用户的内容-----");
		// System.out.println(user);
		// System.out.println(user.getAccounts());
		// }
	}
	private InputStream in;
	private SqlSessionFactory factory;
	private SqlSession session;
	private IUserDao userDao;
	@Before//junit 的注解
	public void init()throws Exception{
		//1.读取配置文件
		in = Resources.getResourceAsStream("SqlMapConfig.xml");
		//2.创建工厂
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		factory = builder.build(in);
		//3.创建 session
		session = factory.openSession();
		//4.创建代理对象
		userDao = session.getMapper(IUserDao.class);
	}
	@After//junit 的注解
	public void destroy()throws Exception {
		//提交事务
		session.commit();
		//释放资源
		session.close();
		//关闭流
		in.close();
	}
}
小结

一对一(多对一)、一对多等这种查询就是实质上就是调用对方的方法来执行

mybatis 基于注解的二级缓存

在 SqlMapConfig 中开启二级缓存支持

<!-- 配置二级缓存 -->
<settings>
	<!-- 开启二级缓存的支持 -->
	<setting name="cacheEnabled" value="true"/>
</settings>

在持久层接口中使用注解配置二级缓存

/**
*
* <p>Title: IUserDao</p>
* <p>Description: 用户的持久层接口</p>
* <p>Company: http://www.itheima.com/ </p>
*/
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值