超详细的Mybatis学习笔记

本文详细介绍了MyBatis框架的基本概念、安装配置方法及其核心功能,包括动态SQL、参数处理、结果映射等方面,帮助读者快速掌握MyBatis的使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

Mybatis框架

1. 框架概述

1.1 框架是什么

它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题,使用框架的好处:

框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率

1.2 三层架构

  1. 表现层:用于展示数据

  2. 业务层:处理业务需求

  3. 持久层:是和数据库交互

1.2 持久层的技术解决方案

  1. JDBC技术:

    • Connection
    • PreparedStatement
    • ResultSet
  2. Spring的JdbcTemplate:

    Spring中对jdbc的简单封装

  3. Apache的DBUtils:

    它和Spring的JdbcTemplate很像,也是对Jdbc的简单封装

注意:

  1. 以上这些都不是框架
  2. JDBC是规范
  3. Spring的JdbcTemplate和Apache的DBUtils都是工具类

1.3 Mybatis框架概述

  1. mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
  2. mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
  3. 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。 为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义Mybatis框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。

ORM:

Object Relational Mapping对象关系映射
简单来说:就是把数据库表和实体类及实体类的属性对应起来, 让我们可以操作实体类就实现操作数据库表

user User

id userId

user_name username

需要将实体类的属性和数据库表的字段名称保持一致

user user

id id

user_name user_name

2. Mybatis入门及环境搭建

2.1 前期准备

创建用户表

CREATE TABLE `myuser` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` datetime default NULL COMMENT '生日',
  `sex` char(1) default NULL COMMENT '性别',
  `address` varchar(256) default NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `myuser`(`id`,`username`,`birthday`,`sex`,`address`) values 
(41,'老王','2018-02-27 17:47:08','男','北京'),
(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),
(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),
(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),
(46,'老王','2018-03-07 17:37:26','男','北京'),
(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');

2.2 基于xml的Mybatis使用案例

1、创建maven工程

2、添加Mybatis3.4.5的坐标

<?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>com.uestc</groupId>
    <artifactId>day01_mybaits01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
    </dependencies>
</project>

3、编写User实体类

package com.uestc.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date brithday;
    private String sex;
    private String address;
    public User(){};
    public User(Integer id, String username, Date brithday, String sex, String address) {
        this.id = id;
        this.username = username;
        this.brithday = brithday;
        this.sex = sex;
        this.address = 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 getBrithday() {
        return brithday;
    }

    public void setBrithday(Date brithday) {
        this.brithday = brithday;
    }

    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 + '\'' +
                ", brithday=" + brithday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

4、编写持久层接口IUserDao

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有操作
     */
    public List<User> findAll();

}

5、编写持久层接口的映射文件IUserDao.xml

<!--要求: 创建位置:必须和持久层接口在相同的包中。 
名称:必须以持久层接口名称命名文件名,扩展名是.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.uestc.dao.IUserDao">
    <!--配置查询所有-->
    <select id="findAll" resultType="com.uestc.domain.User">
        select * from user
    </select>
</mapper>

6、编写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">
                <!--配置数据库的四个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
                <property name="username" value="root"></property>
                <property name="password" value="287216"></property>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/uestc/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>

7、编写测试类

  1. 读取配置文件
  2. 创建SqlSessionFactory工厂
  3. 使用工厂生产Sqlsession对象
  4. 使用Sqlssion创建Dao接口的代理对象IUserDao
  5. 使用代理对象执行方法
  6. 释放资源

注意事项:不要忘记在映射配置中告知mybatis要封装到哪个实体类中

配置的方式:指定实体类的全限定类名

package com.uestc.test;
import com.uestc.dao.IUserDao;
import com.uestc.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 java.io.InputStream;
import java.util.List;
public class MybatisTest {
    /**
     * 入门案例
     * @param args
     */
    public static void main (String[] args) throws Exception{
        //1.读取配置文件
        InputStream is=Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
       SqlSessionFactory factory=builder.build(is);
        //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();
        is.close();
    }
}

总结

步骤:

  1. 创建maven工程并导入坐标
  2. 创建实体类和dao的接口
  3. 创建Mybatis的主配置文件:SqlMapConfig.xml
  4. 创建映射配置文件:IUserDao.xml

注意事项

  1. 创建IUserDao.xmlIUserDao.java时名称是为了和我们之前的知识保持一致,在Mybatis中它把持久层的操作接口名称和映射文件也叫作Mapper, 所以IUserDaoIUserMapper是一样的
  2. 在IDEA中创建目录时,它和包是不一样的,包在创建时:com.uestc.dao是三级目录,而目录在创建时:com.uestc.dao是一级目录
  3. mybaits的映射配置文件位置必须和dao接口的包结构相同
  4. 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
  5. 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

当我们遵从了第3,4,5点之后,我们在开发中就无序再写dao的实现类

2.3 基于注解的mybatis使用案例

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

注意:我们实际在开发中,都是越简便越好,所以都是采用不写dao实现类的方式。

不管使用XML还是注解配置,但Mybatis是支持写dao实现类的。

1、在持久层接口中添加注解

package com.uestc.dao;
import com.uestc.domain.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有操作
     */
    @Select("select * from user")
    public List<User> findAll();

}

2、修改 SqlMapConfig.xml

使用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">
                <!--配置数据库的四个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
                <property name="username" value="root"></property>
                <property name="password" value="287216"></property>
            </dataSource>
        </environment>
    </environments>

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

3. 自定义Mybatis框架

本章我们将使用前面所学的基础知识来构建一个属于自己的持久层框架,将会涉及到的一些知识点:工厂模式(Factory工厂模式)构造者模式(Builder模式)代理模式反射自定义注解注解的反射xml解析数据库元数据元数据的反射等。

3.1 入门案例分析

mybatis在使用代理dao的方式实现增删改查时做什么事呢?

两件事情:

  1. 创建代理对象
  2. 在代理对象中调用selectList

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

  • class Resource

  • class SqlSessionFactoryBuilder

  • interface SqlSessionFactory

  • interface SqlSession

分析流程

1、selectList方法分析()

getMapper方法分析

项目结构:

3.2 前期准备

step1: 创建Maven工程

step2: 导入坐标

<?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>com.uestc</groupId>
    <artifactId>day01_mybaits01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version> 1.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>
</project>

step3: 引入工具类到项目中

工具类:XMLConfigBuilder

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 *  用于解析配置文件
 */
public class XMLConfigBuilder {
    /**
     * 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
     * 使用的技术:
     *      dom4j+xpath
     */
    public static Configuration loadConfiguration(InputStream config){
        try{
            //定义封装连接信息的配置对象(mybatis的配置对象)
            Configuration cfg = new Configuration();

            //1.获取SAXReader对象
            SAXReader reader = new SAXReader();
            //2.根据字节输入流获取Document对象
            Document document = reader.read(config);
            //3.获取根节点
            Element root = document.getRootElement();
            //4.使用xpath中选择指定节点的方式,获取所有property节点
            List<Element> propertyElements = root.selectNodes("//property");
            //5.遍历节点
            for(Element propertyElement : propertyElements){
                //判断节点是连接数据库的哪部分信息
                //取出name属性的值
                String name = propertyElement.attributeValue("name");
                if("driver".equals(name)){
                    //表示驱动
                    //获取property标签value属性的值
                    String driver = propertyElement.attributeValue("value");
                    cfg.setDriver(driver);
                }
                if("url".equals(name)){
                    //表示连接字符串
                    //获取property标签value属性的值
                    String url = propertyElement.attributeValue("value");
                    cfg.setUrl(url);
                }
                if("username".equals(name)){
                    //表示用户名
                    //获取property标签value属性的值
                    String username = propertyElement.attributeValue("value");
                    cfg.setUsername(username);
                }
                if("password".equals(name)){
                    //表示密码
                    //获取property标签value属性的值
                    String password = propertyElement.attributeValue("value");
                    cfg.setPassword(password);
                }
            }
            //取出mappers中的所有mapper标签,判断他们使用了resource还是class属性
            List<Element> mapperElements = root.selectNodes("//mappers/mapper");
            //遍历集合
            for(Element mapperElement : mapperElements){
                //判断mapperElement使用的是哪个属性
                Attribute attribute = mapperElement.attribute("resource");
                if(attribute != null){
                    System.out.println("使用的是XML");
                    //表示有resource属性,用的是XML
                    //取出属性的值
                    String mapperPath = attribute.getValue();//获取属性的值"com/itheima/dao/IUserDao.xml"
                    //把映射配置文件的内容获取出来,封装成一个map
                    Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath);
                    //给configuration中的mappers赋值
                    cfg.setMappers(mappers);
                }else{
                    System.out.println("使用的是注解");
                    //表示没有resource属性,用的是注解
                    //获取class属性的值
                    String daoClassPath = mapperElement.attributeValue("class");
                    //根据daoClassPath获取封装的必要信息
                    Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
                    //给configuration中的mappers赋值
                    cfg.setMappers(mappers);
                }
            }
            //返回Configuration
            return cfg;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            try {
                config.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

    }

    /**
     * 根据传入的参数,解析XML,并且封装到Map中
     * @param mapperPath    映射配置文件的位置
     * @return  map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
     *          以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
     */
    private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {
        InputStream in = null;
        try{
            //定义返回值对象
            Map<String,Mapper> mappers = new HashMap<String,Mapper>();
            //1.根据路径获取字节输入流
            in = Resources.getResourceAsStream(mapperPath);
            //2.根据字节输入流获取Document对象
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //3.获取根节点
            Element root = document.getRootElement();
            //4.获取根节点的namespace属性取值
            String namespace = root.attributeValue("namespace");//是组成map中key的部分
            //5.获取所有的select节点
            List<Element> selectElements = root.selectNodes("//select");
            //6.遍历select节点集合
            for(Element selectElement : selectElements){
                //取出id属性的值      组成map中key的部分
                String id = selectElement.attributeValue("id");
                //取出resultType属性的值  组成map中value的部分
                String resultType = selectElement.attributeValue("resultType");
                //取出文本内容            组成map中value的部分
                String queryString = selectElement.getText();
                //创建Key
                String key = namespace+"."+id;
                //创建Value
                Mapper mapper = new Mapper();
                mapper.setQueryString(queryString);
                mapper.setResultType(resultType);
                //把key和value存入mappers中
                mappers.put(key,mapper);
            }
            return mappers;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            in.close();
        }
    }

    /**
     * 根据传入的参数,得到dao中所有被select注解标注的方法。
     * 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息
     * @param daoClassPath
     * @return
     */
    private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
        //定义返回值对象
        Map<String,Mapper> mappers = new HashMap<String, Mapper>();

        //1.得到dao接口的字节码对象
        Class daoClass = Class.forName(daoClassPath);
        //2.得到dao接口中的方法数组
        Method[] methods = daoClass.getMethods();
        //3.遍历Method数组
        for(Method method : methods){
            //取出每一个方法,判断是否有select注解
            boolean isAnnotated = method.isAnnotationPresent(Select.class);
            if(isAnnotated){
                //创建Mapper对象
                Mapper mapper = new Mapper();
                //取出注解的value属性值
                Select selectAnno = method.getAnnotation(Select.class);
                String queryString = selectAnno.value();
                mapper.setQueryString(queryString);
                //获取当前方法的返回值,还要求必须带有泛型信息
                Type type = method.getGenericReturnType();//List<User>
                //判断type是不是参数化的类型
                if(type instanceof ParameterizedType){
                    //强转
                    ParameterizedType ptype = (ParameterizedType)type;
                    //得到参数化类型中的实际类型参数
                    Type[] types = ptype.getActualTypeArguments();
                    //取出第一个
                    Class domainClass = (Class)types[0];
                    //获取domainClass的类名
                    String resultType = domainClass.getName();
                    //给Mapper赋值
                    mapper.setResultType(resultType);
                }
                //组装key的信息
                //获取方法的名称
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String key = className+"."+methodName;
                //给map赋值
                mappers.put(key,mapper);
            }
        }
        return mappers;
    }

}

工具类:Executor

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 * 负责执行SQL语句,并且封装结果集
 */
public class Executor {

    public <E> List<E> selectList(Mapper mapper, Connection conn) {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1.取出mapper中的数据
            String queryString = mapper.getQueryString();//select * from user
            String resultType = mapper.getResultType();//com.itheima.domain.User
            Class domainClass = Class.forName(resultType);
            //2.获取PreparedStatement对象
            pstm = conn.prepareStatement(queryString);
            //3.执行SQL语句,获取结果集
            rs = pstm.executeQuery();
            //4.封装结果集
            List<E> list = new ArrayList<E>();//定义返回值
            while(rs.next()) {
                //实例化要封装的实体类对象
                E obj = (E)domainClass.newInstance();

                //取出结果集的元信息:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //取出总列数
                int columnCount = rsmd.getColumnCount();
                //遍历总列数
                for (int i = 1; i <= columnCount; i++) {
                    //获取每列的名称,列名的序号是从1开始的
                    String columnName = rsmd.getColumnName(i);
                    //根据得到列名,获取每列的值
                    Object columnValue = rs.getObject(columnName);
                    //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
                    PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
                    //获取它的写入方法
                    Method writeMethod = pd.getWriteMethod();
                    //把获取的列的值,给对象赋值
                    writeMethod.invoke(obj,columnValue);
                }
                //把赋好值的对象加入到集合中
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm,rs);
        }
    }
    
    private void release(PreparedStatement pstm,ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

        if(pstm != null){
            try {
                pstm.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

工具类:DataSourceUtil

package com.uestc.mybatis.utils;
import com.uestc.mybatis.cfg.Configuration;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
 * 用于创建数据源的工具类
 */
public class DataSourceUtil {
    public static Connection getConnection(Configuration cfg){
        try {
            Class.forName(cfg.getDriver());
            return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
        } catch (Exception e){
            throw new RuntimeException(e);
        }

    }
}

step4: 编写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">
                <!--配置数据库的四个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
                <property name="username" value="root"></property>
                <property name="password" value="287216"></property>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/uestc/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>
<!-- 注意: 此处我们直接使用的是mybatis的配置文件,但是由于我们没有使用mybatis的jar包,所以要把配置文件的约束删掉否则会报错(如果电脑能接入互联网,不删也行-->

step5: 编写读取配置文件类 Resources

package com.uestc.mybatis.io;
import java.io.InputStream;
/**
 * 使用类加载器读取配置文件的类
 */
public class Resources {
    /**
     * 用于加载xml文件,并且得到一个流对象
     * @param xmlPath
     * @return
     */
    public static InputStream getResourceAsStream(String xmlPath){
        return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
    }
}

step6: 编写Mapper类

package com.uestc.mybatis.cfg;
/**
 * 用于封装执行的SQL语句和结果类型的全限定类名
 */
public class Mapper {
    private String queryString;//SQL
    private String resultType;//实体类的全限定类名
    public String getQueryString() {
        return queryString;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }
}

step7: 编写Configuration配置类

/**
 * 核心配置类:
 *  1.数据库信息 
 *	2.sql的map集合
 * 自定义mybatis的配置类
 */
public class Configuration {
    private String driver;
    private String url;
    private String username;
    private String password;
	//map集合 Map<唯一标识,Mapper> 用于保存映射文件中的sql标识及sql语句
    private Map<String, Mapper> mappers=new HashMap<String, Mapper>();

    public Map<String, Mapper> getMappers() {
        return mappers;
    }

    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers.putAll(mappers);//此处需要使用追加的方式
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

step8: 编写User实体类

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    public User(){};
    public User(Integer id, String username, Date brithday, String sex, String address) {
        this.id = id;
        this.username = username;
        this.birthday = brithday;
        this.sex = sex;
        this.address = 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 brithday) {
        this.birthday = brithday;
    }

    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 + '\'' +
                ", brithday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

3.3 基于XML的自定义mybatis框架

step1: 编写持久层接口IUserDao和IUserDao.xml

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有操作
     */
    public List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.uestc.dao.IUserDao">
    <!--配置查询所有-->
    <select id="findAll" resultType="com.uestc.domain.User">
        select * from user
    </select>
</mapper>
<!-- 注意: 此处我们使用的也是mybatis的配置文件,所以也要把约束删除了-->

step2: 编写构建者类

/**
 * 用于创建sqlSessionFactory对象
 */
public class SqlSessionFactoryBuilder {
    /**
     * 根据参数的字节输入流构建一个sqlSessionFactory工厂
     * @param config 它就是SqlMapConfig.xml的配置以及里面包含的IUserDao.xml的配置
     * @return
     */
    public SqlSessionFactory build(InputStream config){
        Configuration cfg= XMLConfigBuilder.loadConfiguration(config);
        SqlSessionFactory factory=new  DefaultSqlSessionFactory(cfg);
        return factory ;
    }
}

step3: 编写SqlSessionFactory接口和实现类

package com.uestc.mybatis.sqlSession;
public interface SqlSessionFactory {
    /**
     * 用于打开一个新的SqlSession对象
     * @return
     */
    SqlSession openSession();
}
package com.uestc.mybatis.sqlSession.defaults;
public class DefualtSqlSessionFactory implements SqlSessionFactory{
    private Configuration cfg;
    public DefualtSqlSessionFactory(Configuration cfg){
        this.cfg=cfg;
    }
    /**
     * 用于创建一个新的操作数据库对象
     * @return
     */
    public SqlSession openSession() {
        return new DefaultSqlSession(cfg);
    }
}

step4: 编写SqlSession接口和实现类

package com.uestc.mybatis.sqlSession;

/**
* 自定义Mybatis中和数据库交互的核心类
* 它里面可以创建dao接口的代理对象
*/
public interface SqlSession {
   /**
    * 根据参数创建一个代理对象
    * @param daoInterfaceClass dao的接口字节码
    * @param <T>
    * @return
    */
   <T> T getMapper(Class<T> daoInterfaceClass);

   /**
    * 释放资源
    */
   void close();
}

package com.uestc.mybatis.sqlSession.defaults;
/**
 * SqlSession接口的实现类
 */
public class DefaultSqlSession implements SqlSession {
    private Configuration cfg;
    private Connection conn;
    public DefaultSqlSession(Configuration cfg){
        this.cfg=cfg;
        conn= DataSourceUtil.getConnection(cfg);
    }
    /**
     *动态代理: 
     * 涉及的类:Proxy 
     * 使用的方法:newProxyInstance 
     * 方法的参数:
     * ClassLoader:和被代理对象使用相同的类加载器,通常都是固定的 
     * Class[]:代理对象和被代理对象要求有相同的行为。(具有相同的方法) 
     * InvocationHandler:如何代理。需要我们自己提供的增强部分的代码
     * 用于创建代理对象
     * @param daoInterfaceClass dao的接口字节码
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> daoInterfaceClass) {
        return (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),conn));
    }

    /**
     * 用于释放资源
     */
    public void close() {
        try {
            if(conn!=null){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

step5: 编写用于创建Dao接口代理对象的类

package com.uestc.mybatis.sqlSession.proxy;
public class MapperProxy implements InvocationHandler {
    //mao的key是全限定类名+方法名
    private Map<String,Mapper> mappers;
    private Connection conn;
    public MapperProxy(Map<String,Mapper> mappers, Connection conn){
        this.mappers=mappers;
        this.conn=conn;
    }
    /**
     * 用于对方法进行增强,我们的增强其实就是调用seLectList方法
     *
     * 对当前正在执行的方法进行增强
     * 取出当前执行的方法名称
     * 取出当前执行的方法所在类
     * 拼接成key
     * 去Map中获取Value(Mapper)
     * 使用工具类Executor的selectList方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1.获取方法名
        String methodName=method.getName();
        //2.获取方法所在类的名称
        String className=method.getDeclaringClass().getName();
        //3.组合key
        String key=className+"."+methodName;
        //4.获取mappers中的Mapper对象
        Mapper mapper=mappers.get(key);

        //5.判断是否有mapper
        if(mapper==null){
            throw new IllegalArgumentException("传入参数有误");
        }
        //6.调用工具类查询所有
        return new Executor().selectList(mapper,conn);
    }
}

step6: 编写测试类

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

    }
}

3.4 基于注解方式定义Mybatis框架

step1: 自定义@Select注解

package com.uestc.mybatis.annotations;
/**
 * 查询注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    /**
     * 配置SQL语句的
     */
    String value();
}

step2: 修改持久层接口

package com.uestc.dao;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有操作
     */
    @Select("select * from user")
    public List<User> findAll();
}

step3: 修改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">
                <!--配置数据库的四个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
                <property name="username" value="root"></property>
                <property name="password" value="287216"></property>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <!--<mapper resource="com/uestc/dao/IUserDao.xml"></mapper>-->
        <mapper class="com.uestc.dao.IUserDao"></mapper>
    </mappers>
</configuration>

自定义流程再分析

4. 基于代理Dao实现CRUD操作

4.1 mybatis环境搭建步骤

项目目录:

1、创建maven工程

2、导入坐标

3、 编写必要代码(实体类和持久层接口)

4、编写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">

<configuration>
    <!--配置环境-->
    <environments default="mysql">
        <!--配置mysql环境-->
        <environment id="mysql">
            <!--配置事务-->
            <transactionManager type="jdbc"></transactionManager>
            <!--配置连接池-->
            <dataSource type="POOLED" >
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
                <property name="username" value="root"></property>
                <property name="password" value="287216"></property>
            </dataSource>
        </environment>
    </environments>
    <!--配置映射文件的位置-->
    <mappers>
        <mapper resource="com/uestc/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>

5、编写映射配置文件

6、编写测试类

4.2 使用要求:

  1. 持久层接口和持久层接口的映射配置必须在相同的包下
  2. 持久层映射配置中mapper标签的namespace属性取值必须是持久层接口的全限定类名
  3. SQL语句的配置标签<select>,<insert>,<delete>,<update>的id属性必须和持久层接口的方法名相同。

4.3 根据ID查询

step1:在持久层接口中添加findById方法

   /**
     * 根据id查询用户
     * @param userId
     * @return
     */
 User findById(Integer userId);

step2: 在用户的映射配置文件中配置

   <!--根据id查询用户-->
    <select id="findById" parameterType="String" resultType="com.uestc.domain.User">
        select * from user where id=#{id}
    </select>

细节

  • resultType属性: 用于指定结果集的类型。

  • parameterType属性:

    用于指定传入参数的类型。 ql语句中使用#{}字符: 它代表占位符,相当于原来jdbc部分所学的?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。

    #{}中内容的写法: 由于数据类型是基本类型,所以此处可以随意写

step3: 在测试类添加测试

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

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
   
    /**
     * 测试根据id查找用户
     */
    @Test
    public void TestFindById(){
        //执行查找功能
        User user=userDao.findById(50);
        System.out.println(user);
    }

4.4 保存操作

step1: 在持久层接口中添加saveUser方法

   /**
     * 保存用户
     * @param user
     */
    void saveUser(User user);

step2: 在用户的映射配置文件中配置

<!--保存用户-->
    <insert id="saveUser" parameterType="com.uestc.domain.User">
        <!--配置插入操作后,获取插入数据的id-->
        <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>

细节

  • parameterType属性: 代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。

  • sql语句中使用#{}字符: 它代表占位符,相当于原来jdbc部分所学的?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。

    #{}中内容的写法: 由于我们保存方法的参数是 一个User对象,此处要写User对象中的属性名称。 它用的是ognl表达式。

  • ognl表达式: 它是apache提供的一种表达式语言,全称是: Object Graphic Navigation Language 对象图导航语言 它是按照一定的语法格式来获取数据的。 语法格式就是使用 `#{对象.对象}的方式

    #{user.username}它会先去找user对象,然后在user对象中找到username属性,并调用getUsername()方法把值取出来。但是我们在parameterType属性上指定了实体类名称,所以可以省略user.而直接写username。

step3: 添加测试类中的测试方法

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

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试保存操作
     */
    @Test
    public void TestSave() throws Exception{
        User user=new User(null,"mybatis saveruser","北京市顺义区","男",new Date());
        System.out.println("保存操作之前:"+user);
        //执行保存方法
        userDao.saveUser(user);

        //提交事务
        sqlSession.commit();
        System.out.println("保存操作之后:"+user);
    }

细节

打开Mysql数据库发现并没有添加任何记录,原因是什么? 这一点和jdbc是一样的,我们在实现增删改时一定要去控制事务的提交,那么在mybatis中如何控制事务提交呢? 可以使用:session.commit();来实现事务提交。加入事务提交后的代码如下:

@After//在测试方法执行完成之后执行
public void destroy() throws Exception{ 
	session.commit(); 
	//7.释放资源 
	session.close(); 
	in.close(); 
}

新增用户id的返回值

<!--新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长auto_increment的值返回。-->
<insert id="saveUser" parameterType="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>

4.5 用户更新

step1: 在持久层接口中添加更新方法

   /**
     * 更新用户
     * @param user
     */
    void updateUser(User user);

step2: 在用户的映射配置文件中配置

<!--更新用户-->
    <update id="updateUser" parameterType="com.uestc.domain.User">
        update user set username=#{username},address=#{address}, sex=#{sex}, 
        birthday=#{birthday} where id=#{id}
    </update>

step3: 加入更新的测试方法

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    
    /**
     * 测试更新操作
     */
    @Test
    public void TestUpdate(){
        User user=new User(50,"宝贝","北京市海淀区","女",new Date());
        userDao.updateUser(user);
        sqlSession.commit();
    }
}

4.6 用户删除

step1: 在持久层接口中添加删除方法

/**
  * 删除用户
  * @param userId
  */
  void deleteUser(Integer userId);

step2: 在用户的映射配置文件中配置

 <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>

step3: 加入删除的测试方法

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试删除操作
     */
    @Test
    public void TestDelete(){
        //执行删除功能
        userDao.deleteUser(48);
        sqlSession.commit();
    }
}

4.7 用户模糊查询

step1: 在持久层接口中添加模糊查询方法

/**
     * 根据名称模糊查询用户
     * @param name
     * @return
     */
    List<User> findByName(String name);

step2: 在用户的映射配置文件中配置

 <!--根据姓名模糊查询-->
    <select id="findByName" parameterType="String" resultType="com.uestc.domain.User" >
         select * from user where username like #{name}
    </select>

step3: 加入模糊查询的测试方法

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
   
    /**
     * 测试模糊查询
     */
    @Test
    public void TestFindByName(){
        //执行查找功能
        List<User> users=userDao.findByName("%王%");
        for(User user:users){
            System.out.println(user);
        }
    }
}

模糊查询的另一种配置方式

第一步:修改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(){
        //执行查找功能
        List<User> users=userDao.findByName("王");
        for(User user:users){
            System.out.println(user);
        }
    }

#{}与${}的区别

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

  • ${}表示拼接sql串 通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, 可 以 接 收 简 单 类 型 值 或 p o j o 属 性 值 , 如 果 p a r a m e t e r T y p e 传 输 单 个 简 单 类 型 值 , {}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值, pojoparameterType{}括号中只能是value

4.8 查询使用聚合函数

step1: 在持久层接口中添加count方法

/**
  * 查询总用户数
  * @return
  */
  int findTotal();

step2: 在用户的映射配置文件中配置

<!--查询总用户数-->
<select id="findTotal" resultType="int">
    select count(id) from user
</select>

step3 :加入聚合查询的测试方法

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
   
    /**
     * 测试总用户数
     */
    @Test
    public void TestFindTotal() {
        //执行查找功能
        int total = userDao.findTotal();
        System.out.println(total);
    }
}

4.9 总结

IUserDao接口类

package com.uestc.dao;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有
     * @return
     */
    List<User> findAll();

    /**
     * 保存用户
     * @param user
     */
    void saveUser(User user);

    /**
     * 更新用户
     * @param user
     */
    void updateUser(User user);

    /**
     * 删除用户
     * @param userId
     */

    void deleteUser(Integer userId);

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    User findById(Integer userId);

    /**
     * 根据名称模糊查询用户
     * @param name
     * @return
     */
    List<User> findByName(String name);

    /**
     * 查询总用户数
     * @return
     */
    int findTotal();
}

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.uestc.dao.IUserDao">
    <!-- 查询所有-->
    <select id="findAll" resultType="com.uestc.domain.User">
        select * from user
    </select>
    <!--保存用户-->
    <insert id="saveUser" parameterType="com.uestc.domain.User">
        <!--配置插入操作后,获取插入数据的id-->
        <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.uestc.domain.User">
        update user set username=#{username},address=#{address}, sex=#{sex}, birthday=#{birthday} where id=#{id}
    </update>

    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>

    <!--根据id查询用户-->
    <select id="findById" parameterType="String" resultType="com.uestc.domain.User">
        select * from user where id=#{id}
    </select>

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

    <!--查询总用户数-->
    <select id="findTotal" resultType="int">
        select count(id) from user
    </select>
</mapper>

测试类

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试查询所有
     */
    @Test
    public void TestFindAll() throws Exception {
        //通过代理对象来执行findAll方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * 测试保存操作
     */
    @Test
    public void TestSave() throws Exception{
        User user=new User(null,"mybatis saveruser","北京市顺义区","男",new Date());
        System.out.println("保存操作之前:"+user);
        //执行保存方法
        userDao.saveUser(user);

        //提交事务
        sqlSession.commit();
        System.out.println("保存操作之后:"+user);
    }

    /**
     * 测试更新操作
     */
    @Test
    public void TestUpdate(){
        User user=new User(50,"宝贝","北京市海淀区","女",new Date());
        userDao.updateUser(user);
        sqlSession.commit();
    }

    /**
     * 测试删除操作
     */
    @Test
    public void TestDelete(){
        //执行删除功能
        userDao.deleteUser(48);
        sqlSession.commit();
    }
    /**
     * 测试根据id查找用户
     */
    @Test
    public void TestFindById(){
        //执行查找功能
        User user=userDao.findById(50);
        System.out.println(user);
    }
    /**
     * 测试模糊查询
     */
    @Test
    public void TestFindByName(){
        //执行查找功能
        //List<User> users=userDao.findByName("%王%");
        List<User> users=userDao.findByName("王");
        for(User user:users){
            System.out.println(user);
        }
    }
    /**
     * 测试总用户数
     */
    @Test
    public void TestFindTotal() {
        //执行查找功能
        int total = userDao.findTotal();
        System.out.println(total);
    }
}

4.10 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定义输出结果的类型。

4.11分析代理dao的执行过程

5. Mybatis的参数深入

5.1 Mybatis的参数

5.1.1 parameterType配置参数

我们在上一章节中已经介绍了SQL语句传参,使用标签的parameterType属性来设定。该属性的取值可以是基本类型引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。

注意事项

基本类型String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。

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

5.1.2 传递pojo对象

mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称

OGNL表达式

Object Graphic Navigation Language(对象导航语言)

它是通过对象的取值方法来获取数据再写法上把get给省略了

比如获取用户的名称

在类中的写法:user.getUsername();

OGNL表达式写法:user.Username

mybatis中为什么能直接写username,而不用写user.username呢

因为在parameteType中已经提供了属性所属的类,所以此时不需要写对象名

5.1.3 传递pojo包装对象

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

Pojo类中包含pojo。

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

step1: 编写QueryVo

package com.uestc.domain;
public class QueryVo {
    private User user;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

step2: 编写持久层接口

package com.uestc.dao;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
  
    /**
     * 根据queryVo中的条件查询用户
     * @param vo
     * @return
     */
    int findUserByVo(QueryVo vo);
}

step3: 持久层接口的映射文件

  <!--根据queryVo的条件查询用户-->
    <select id="findUserByVo" parameterType="com.uestc.domain.QueryVo">
        select * from user where id=#{user.username}
    </select>

step4:测试包装类作为参数

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
 
    /**
     * 测试模糊查询
     */
    @Test
    public void TestFindByVo(){
        //执行查找功能
        QueryVo queryVo=new QueryVo();
        User u=new User();
        queryVo.setUser(u);
        u.setUsername("王");
        List<User> users=userDao.findUserByVo(queryVo);
        for(User user:users){
            System.out.println(user);
        }
    }
}

5.2 Mybatis的输出结果封装

5.2.1 resultType配置结果类型

resultType属性可以指定结果集的类型,它支持基本类型实体类类型。 我们在前面的CRUD案例中已经对此属性进行过应用了。

需要注意的是,它和parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)

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

特殊情况示例:此时的实体类属性和数据库表的列名已经不一致了

step1:修改实体类

package com.uestc.domain;
import java.io.Serializable;
import java.util.Date;

public class User implements Serializable{
    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    public User(){};
    public User(Integer userId, String userName, String userAddress, String userSex, Date userBirthday) {
        this.userId = userId;
        this.userName = userName;
        this.userAddress = userAddress;
        this.userSex = userSex;
        this.userBirthday = userBirthday;
    }

    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 String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        this.userBirthday = userBirthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAddress='" + userAddress + '\'' +
                ", userSex='" + userSex + '\'' +
                ", userBirthday=" + userBirthday +
                '}';
    }
}

step2: Dao接口:

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

step3:映射配置:

<!-- 查询所有-->
    <select id="findAll" resultType="com.uestc.domain.User">
        select * from user
    </select>

step4: 测试查询结果

    @Test
    public void TestFindAll() throws Exception {
        //通过代理对象来执行findAll方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

为什么名称会有值呢? 因为:mysql在windows系统中不区分大小写!

5.2.2 修改映射配置

使用别名查询

<!-- 查询所有-->
    <select id="findAll" resultType="com.uestc.domain.User">
        select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user
    </select>

如果我们的查询很多,都使用别名的话写起来岂不是很麻烦

5.2.3 resultMap结果类型

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

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

step1: 定义resultMap

<!--配置查询的列名和实体类的属性名的对应关系-->
    <resultMap id="userMap" type="com.uestc.domain.User">
        <!--主键字段的对应-->
        <id property="userId" column="id"></id>
        <!--非主键字段的对应-->
        <result property="userName" column="username"></result>
        <result property="userAddress" column="address"></result>
        <result property="userSex" column="sex"></result>
        <result property="userBirthday" column="birthday"></result>
    </resultMap>

step2: 映射配置

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

step3: 测试结果

6. Mybatis传统DAO层开发

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

step1: 编写用户实体类User

package com.uestc.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{
    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;
    public User(){};
    public User(Integer id, String username, String address, String sex, Date birthday) {
        this.id = id;
        this.username = username;
        this.address = address;
        this.sex = sex;
        this.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;
    }
    @Override
    public String toString() {
        return "User{" +
                "rId=" + id +
                ", name='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

step2: 编写持久层Dao接口

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有
     * @return
     */
    List<User> findAll();

    /**
     * 保存用户
     * @param user
     */
    void saveUser(User user);

    /**
     * 更新用户
     * @param user
     */
    void updateUser(User user);

    /**
     * 删除用户
     * @param userId
     */

    void deleteUser(Integer userId);

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    User findById(Integer userId);

    /**
     * 根据名称模糊查询用户
     * @param name
     * @return
     */
    List<User> findByName(String name);

    /**
     * 查询总用户数
     * @return
     */
    int findTotal();
}

step3: 编写持久层Dao实现类

package com.uestc.dao.impl;
public class UserDaoImpl implements IUserDao {
    private SqlSessionFactory factory;
    public UserDaoImpl(SqlSessionFactory factory){
        this.factory=factory;
    }
    public List<User> findAll() {
        //1.根据factory获取SqlSesstion对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sesstion的方法实现查询功能
        List<User> users=sqlSession.selectList("com.uestc.dao.IUserDao.findAll");//参数就是能获得配置信息的key
        //3.释放资源
        sqlSession.close();
        return users;
    }

    public void saveUser(User user) {
        //1.根据factory获取SqlSession对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sqlSession对象的方法实现插入操作
        sqlSession.insert("com.uestc.dao.IUserDao.saveUser",user);
        //3.提交事务
        sqlSession.commit();
        //4.释放资源
        sqlSession.close();
    }

    public void updateUser(com.uestc.domain.User user) {
        //1.根据factory获取SqlSession对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sqlSession对象的方法实现更新操作
        sqlSession.update("com.uestc.dao.IUserDao.updateUser",user);
        //3.提交事务
        sqlSession.commit();
        //4.释放资源
        sqlSession.close();
    }

    public void deleteUser(Integer userId) {
        //1.根据factory获取SqlSession对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sqlSession对象的方法实现更新操作
        sqlSession.delete("com.uestc.dao.IUserDao.deleteUser",userId);
        //3.提交事务
        sqlSession.commit();
        //4.释放资源
        sqlSession.close();
    }

    public User findById(Integer userId) {
        //1.根据factory获取SqlSession对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sqlSession对象的方法实现更新操作
        User user=sqlSession.selectOne("com.uestc.dao.IUserDao.findById",userId);
        //3.释放资源
        sqlSession.close();
        return user;
    }

    public List<User> findByName(String name) {
        //1.根据factory获取SqlSession对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sqlSession对象的方法实现更新操作
        List<User> users=sqlSession.selectList("com.uestc.dao.IUserDao.findByName",name);
        //3.释放资源
        sqlSession.close();
        return users;
    }

    public int findTotal() {
        //1.根据factory获取SqlSession对象
        SqlSession sqlSession=factory.openSession();
        //2.调用sqlSession对象的方法实现更新操作
        int count=sqlSession.selectOne("com.uestc.dao.IUserDao.findTotal");
        //3.释放资源
        sqlSession.close();
        return count;
    }
}

step4: 编写持久层映射配置

<?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.uestc.dao.IUserDao">

    <!-- 查询所有-->
    <select id="findAll" resultType="com.uestc.domain.User">
        select * from user
    </select>
    <!--保存用户-->
    <insert id="saveUser" parameterType="com.uestc.domain.User">
        <!--配置插入操作后,获取插入数据的id-->
        <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.uestc.domain.User">
        update user set username=#{username},address=#{address}, sex=#{sex}, birthday=#{birthday} where id=#{id}
    </update>
    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>

    <!--根据id查询用户-->
    <select id="findById" parameterType="int" resultType="com.uestc.domain.User">
    select * from user where id=#{id}
</select>
    <!--根据姓名模糊查询-->
    <select id="findByName" parameterType="String" resultType="com.uestc.domain.User" >
         <!--select * from user where username like #{name}-->
           select * from user where username like '%${value}%'
    </select>

    <!--查询总用户数-->
    <select id="findTotal" resultType="int">
        select count(id) from user
    </select>
</mapper>

step5: 编写测试类

package com.uestc.test;
/**
 * 测试mybatis的CRUD操作
 */
public class MybatisTest {
    private InputStream in;

    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in=Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.获取SqlSessionFactory创建工厂对象
        SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
        //3.使用factory对象创建dao
        userDao=new UserDaoImpl(factory);

    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        in.close();
    }
    /**
     * 测试查询所有
     */
    @Test
    public void TestFindAll() throws Exception {
        //通过代理对象来执行findAll方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * 测试保存操作
     */
    @Test
    public void TestSave() throws Exception{
        User user=new User(null,"小可爱","四川大竹","女",new Date());
        System.out.println("保存操作之前:"+user);
        //执行保存方法
        userDao.saveUser(user);

        System.out.println("保存操作之后:"+user);
    }

    /**
     * 测试更新操作
     */
    @Test
    public void TestUpdate(){
        User user=new User(51,"大宝贝","成都","女",new Date());
        userDao.updateUser(user);
    }

    /**
     * 测试删除操作
     */
    @Test
    public void TestDelete(){
        //执行删除功能
        userDao.deleteUser(54);

    }
    /**
     * 测试根据id查找用户
     */
    @Test
    public void TestFindById(){
        //执行查找功能
        User user=userDao.findById(46);
        System.out.println(user);
    }

    /**
     * 测试模糊查询
     */
    @Test
    public void TestFindByName(){
        //执行查找功能
        //List<User> users=userDao.findByName("%王%");
        List<User> users=userDao.findByName("王");
        for(User user:users){
            System.out.println(user);
        }
    }

    /**
     * 测试总用户数
     */
    @Test
    public void TestFindTotal() {
        //执行查找功能
        int total = userDao.findTotal();
        System.out.println(total);
    }
}

分析编写dao实现类Mybatis的执行过程

7. SqlMapConfig.xml配置文件

7.1 SqlMapConfig.xml中配置的内容和顺序

  • properties(属性)
    • property
  • settings(全局配置参数)
    • setting
  • typeAliases(类型别名)
    • typeAliase
    • package
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境集合属性对象)
    • environment(环境子属性对象)
      • transactionManager(事务管理)
      • dataSource(数据源)
  • mappers(映射器)
    • mapper
    • package

7.2 properties(属性)

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

  1. 标签内部配置连接数据库的信息
  2. 通过属性引用外部配置文件信息

1、标签内部配置

<!--SqlMapConfig.xml-->
<properties  >
    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis"></property>
    <property name="jdbc.username" value="root"></property>
    <property name="jdbc.password" value="287216"></property>
</properties>

2、在classpath下定义db.properties文件

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

并在properties标签配置

<!--SqlMapConfig.xml--> 
<properties resource="db.properties" >
        <!-- resouce属性:常用的
            用于指定配置文件的位置面试按照类路径的写法来写,并且必须存在类类路径下
        url属性:
            是要求按照url的写法来写地址
            URL:Uniform Resource Locator 统一资源定位符 ,它可以唯一标识一个资源路径
            写法:
                http://localhost:8080/mybatis/demo
                协议       主机   端口   URI
            URI:Uniform Resource Identifier 统一资源标识符,它可以在应用中唯一定位一个资源-->
    </properties>

使用

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

<!--IUserDao.xml-->
<!--配置环境-->
    <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>

7.3 typeAliases(类型别名)

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

自定义别名

SqlMapConfig.xml中配置:

 <typeAliases>
 <!--单个别名定义
	typeAliase 用于配置别名 type属性指定的是实体类全限定类名 alias属性指定别名 当指定了别名就不再区分大小写-->
 	<typeAlias type="com.uestc.domain.User" alias="user"></typeAlias>
<!--批量别名定义,扫描整个包下的类,别名为类名
	用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
 	<package name="com.uestc.domain"></package>
 </typeAliases>

7.4 mappers(映射器)

1、<mapper resource=" " />

使用相对于类路径的资源 ,如:

<mapper resource="com/itheima/dao/IUserDao.xml" />

2、<mapper class=" " />

使用mapper接口类路径, 如

<mapper class="com.itheima.dao.UserDao"/>
<!--注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。-->

3、<package name=""/>

注册指定包下的所有mapper接口,如

<package name="cn.itcast.mybatis.mapper"/>
<!--注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。-->

8. mybatis执行过程分析

9. Mybatis连接池与事务深入

我们在前面的WEB课程中也学习过类似的连接池技术,而在Mybatis中也有连接池技术,但是它采用的是自己的连接池技术。在Mybatis的SqlMapConfig.xml配置文件中,通过<dataSource type="pooled">来实现Mybatis中连接池的配置。

9.1 Mybatis中的连接池

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

Mybatis中我们将它的数据源dataSource分为以下几类

可以看出Mybatis将它自己的数据源分为三类:

  • UNPOOLED 不使用连接池的数据源
  • POOLED 使用连接池的数据源
  • JNDI 使用JNDI实现的数据源

mybaits连接池提供了3种方式的配置

配置的位置:主配置文件SqlMapConfig.xml中的dataSource

type属性的取值

  • POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现

  • UNPOOLED采用传统的获取连接的方式,虽然也实现了Javax.sql.DataSource接口,但是并没与使用池技术思想

  • JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器锁能拿到的DataSource是不一样的,注意如果不是web或者maven的war工程,是不能使用的

    tomcat服务器,采用连接池就是dbcp连接池

9.1.1Mybatis中连接的获取过程分析(一)

当我们需要创建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语句时,才会进行。其实这样做我们也可以进一步发现,数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中

9.1.2 Mybatis中pooled的过程(二)

9.2 Mybatis的事务控制

9.2.1 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(); 
}

观察在它在控制台输出的结果:

这是我们的Connection的整个变化过程,通过分析我们能够发现之前的CUD操作过程中,我们都要手动进行事务的提交,原因是setAutoCommit()方法,在执行时它的值被设置为false了,所以我们在CUD操作中,必须通过sqlSession.commit()方法来执行提交操作

9.2.2 Mybatis自动提交事务的设置

为什么CUD过程中必须使用sqlSession.commit()提交事务?

主要原因就是在连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了JDBC中的connection.commit()方法实现事务提交。

@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(true); 
	//5.创建Dao的代理对象 
	userDao = session.getMapper(IUserDao.class); 
}

所对应的DefaultSqlSessionFactory类的源代码:

10. Mybatis的动态SQL语句

10.1 动态SQL之<if>标签

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

step1: 持久层Dao接口

/**
  * 根据传入的参数条件查询
  * @return user 查询条件,有可能有用户名,有可能有性别,有可能有地址,也可能都有或者都没有
  */
List<User> findUserByCondition(User user);

step2: 持久层Dao映射配置

 <!--根据条件查询-->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
    select * from user where 1=1
    <if test="userName!=null">
        and username=#{userName}
    </if>
    <if test="userSex!=null">
        and sex=#{userSex}
    </if>
</select>

注意:<if>标签的test属性中写的是对象的属性名,如果是包装类的对象要使用OGNL表达式的写法。 另外要注意where 1=1 的作用~!

step3: 测试

@Test
public void TestFindByCondition(){
    User user=new User();
    user.setUserName("老王");
    user.setUserSex("女");
    List<User> users=userDao.findUserByCondition(user);
    for(User u:users){
        System.out.println(u);
    }
}

10.2 动态SQL之<where>标签

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

<!--根据条件查询-->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
    select * from user
    <where>
        <if test="userName!=null">
            and username=#{userName}
        </if>
        <if test="userSex!=null">
            and sex=#{userSex}
        </if>
    </where>
</select>

10.3 动态标签之<foreach>标签

SQL语句:

select 字段`from user where id in (?) 

<foreach>标签用于遍历集合

它的属性:

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符

需求: 传入多个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)

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

步骤

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

package com.uestc.domain;
import java.util.List;
public class QueryVo {
    private List<Integer> ids;
    public List<Integer> getIds() {
        return ids;
    }
    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}

step2: 在持久层Dao接口中添加findUserInIds方法

/**
  * 根据QueryVo中的id查询用户
  * @param queryVo
  * @return
  */
List<User> findUserInIds(QueryVo queryVo);

step3: 持久层Dao映射配置

<!--根据QueryVo中的id集合查询用户列表-->
<select id="findUserInIds" resultMap="userMap" parameterType="queryVo">
    select * from user
    <where>
        <if test="ids!=null and ids.size()>0">
            <foreach collection="ids" open="and id in(" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>

step4: 编写测试方法

//测试foreach标签的使用
@Test
public void TestFindInIds(){
    QueryVo queryVo=new QueryVo();
    List<Integer> list=new ArrayList<Integer>();
    list.add(41);
    list.add(42);
    list.add(43);
    queryVo.setIds(list);
    List<User> users=userDao.findUserInIds(queryVo);
    for(User u:users){
        System.out.println(u);
    }
}

10.4 Mybatis中简化编写的SQL片段

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

步骤

step1: 定义代码片段

<!--抽取重复的sql-->
<sql id="defaultSql">
	select * from user
</sql>

step2: 引用代码片段

 <!-- 查询所有-->
 <select id="findAll" resultMap="userMap">
 	<include refid="defaultSql"></include>
 </select>

11. Mybatis 多表查询之一对多

表之间的关系有多种:一对多多对一一对一多对多
举例:

用户和订单就是一对多:一个用户可以下多个订单

订单和用户就是多对一:多个订单属于同一个用户

人和身份证就是一对一:一个人只能有一个身份证号,而一个身份证号只能属于一个人

老师和学生之间就是多对多:一个学生可以被多个老师教,一个老师可以教多个学生

特例:如果拿出每一个订单,他都只能属于一个用户,所有Mybatis就把多对一看成了一对一。

11.1 示例:用户和账户

一个用户可以有多个账户,一个账户只能属于一个用户

步骤

1、 建立两张表:用户表,账户表
让用户表和账户表之间具备一对多的关系:需要在账户表中添加外键
2、建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出一对多的关系
3、建立两个配置文件:用户的配置文件和账户的配置文件

  • 当我们查询用户时,可以同时得到用户下所包含的账户信息
  • 当我们查询账户时,可以得到账户的所属的用户信息

step1: 数据准备

1、建立账户表:account

CREATE TABLE `account` (
  `ID` int(11) NOT NULL COMMENT '编号',
  `UID` int(11) default NULL COMMENT '用户编号',
  `MONEY` double default NULL COMMENT '金额',
  PRIMARY KEY  (`ID`),
  KEY `FK_Reference_8` (`UID`),
  CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);

2、建立用户表

CREATE TABLE `user` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` datetime default NULL COMMENT '生日',
  `sex` char(1) default NULL COMMENT '性别',
  `address` varchar(256) default NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

11.2 一对一查询(多对一)

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

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

方法一

step1:定义账户信息的实体类 和 用户实体类

//账户实体类
package com.uestc.domain;
import java.io.Serializable;
public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    public Account(){};

    public Account(Integer id, Integer uid, Double money) {
        this.id = id;
        this.uid = uid;
        this.money = 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 +
                '}';
    }
}

//用户实体类
package com.uestc.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{
    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;
    public User(){};
    public User(Integer id, String username, String address, String sex, Date birthday) {
        this.id = id;
        this.username = username;
        this.address = address;
        this.sex = sex;
        this.birthday = birthday;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsernamesername(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;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

step2编写Sql语句

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

在MySQL中测试的查询结果如下:

step3 : 定义AccountUser类

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

package com.uestc.domain;
public class AccountUser extends Account{
    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 + '\'' +
                '}';
    }
}

step4 : 定义账户的持久层Dao接口

package com.uestc.dao;
import com.uestc.domain.Account;
import com.uestc.domain.AccountUser;
import java.util.List;
public interface IAccountDao {
    /**
     * 查询所有账户,同时还要获取到当前账户的所属的用户信息
     * @return
     */
    List<Account> findAll();
    /**
     * 查询所有用户,并且带有用户名称和地址信息
     * @return
     */
    List<AccountUser> findAllAcount();
}

step5 :定义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">
<mapper namespace="com.uestc.dao.IAccountDao">
    <!-- 查询所有,同时包含用户名和地址信息-->
    <select id="findAllAcount" resultType="AccountUser">
         select a.*, u.username, u.address from user u, account a where u.id=a.uid
    </select>
</mapper>

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

step6: 创建AccountTest测试类

package com.uestc.test;
public class AccountTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IAccountDao accountDao;
    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        accountDao=sqlSession.getMapper(IAccountDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试findAllAcount
     */
    @Test
    public void TestFindAllAcount(){
        List<AccountUser> accountUsers =accountDao.findAllAcount();
        for (AccountUser accountUser: accountUsers) {
            System.out.println(accountUser);
        }
    }
}

方式二

使用resultMap,定义专门的resultMap用于映射一对一查询结果。 通过面向对象的(has a)关系可以得知,我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。

step1: 修改Account类

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

package com.uestc.domain;
import java.io.Serializable;
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 Account(){};

    public Account(Integer id, Integer uid, Double money) {
        this.id = id;
        this.uid = uid;
        this.money = 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 +
                '}';
    }
}

steo2: 修改AccountDao接口中的方法

package com.uestc.dao;
import com.uestc.domain.Account;
import com.uestc.domain.AccountUser;
import java.util.List;

public interface IAccountDao {
    /**
     * 查询所有账户,同时还要获取到当前账户的所属的用户信息
     * @return
     */
    List<Account> findAll();
}

注意:第二种方式,将返回值改 为了Account类型。 因为Account类中包含了一个User类的对象,它可以封装账户所对应的用户信息。

step3: 重新定义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">
<mapper namespace="com.uestc.dao.IAccountDao">
    <!--定义封装account和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的内容-->
        <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.money from user u, account a where u.id=a.uid;
    </select>
</mapper>

step4: 在AccountTest类中加入测试方法

/**
 * 测试findAll
 */
@Test
public void TestFindAll(){
    List<Account> accounts =accountDao.findAll();
    for (Account account: accounts) {
        System.out.println("-------每个account的信息------");
        System.out.println(account);
        System.out.println(account.getUser());
    }
}

11.3 一对多查询

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

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

step1: 编写SQL语句

select 
	u.*, 
	a.id as aid, 
	a.uid, 
	a.money 
from 
	user u 
left join account a on u.id=a.uid;

测试该SQL语句在MySQL客户端工具的查询结果如下

step2: User类加入List<Account>

package com.uestc.domain;
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;
    }
    public User(){};

    public User(Integer id, String username, String address, String sex, Date birthday) {
        this.id = id;
        this.username = username;
        this.address = address;
        this.sex = sex;
        this.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;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

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

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

step4: 用户持久层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">
<mapper namespace="com.uestc.dao.IUserDao">
    <!--定义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>
        <!--配置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="usermap">
         select u.*, a.id as aid, a.uid, a.money from user u left join account a on u.id=a.uid
    </select>
</mapper>

collection部分定义了用户关联的账户信息。表示关联查询结果集

  • property="accList": 关联查询的结果集存储在User对象的上哪个属性

  • ofType="account": 指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。

step5: 测试方法

package com.uestc.test;
public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        System.out.println(sqlSession);
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试findByID
     */
    @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());
        }
    }
}

12. Mybatis 多表查询之多对多

12.1 实现Role 到User 多对多

用户与角色的关系模型

用户与角色的多对多关系模型如下:

步骤

  1. 建立两张表:用户表,角色表
    让用户表和角色表之间具备多对多的关系:需要使用中间表,中间表中分别包含指向两张表主键的外键
  2. 建立两个实体类:用户实体类和角色实体类
    • 让用户和角色的实体类能体现出多对多的关系
    • 各自包含对方的一个集合引用
  3. 建立两个配置文件:用户的配置文件和角色的配置文件
    • 当我们查询用户时,可以同时得到用户下所包含的角色信息
    • 当我们查询角色时,可以得到角色的所赋予的用户信息

数据准备

在MySQL 数据库中添加角色表,用户角色的中间表。

角色表:

CREATE TABLE `role` (
  `ID` int(11) NOT NULL COMMENT '编号',
  `ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
  `ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into 
	`role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) 
values 
	(1,'院长','管理整个学院'),
	(2,'总裁','管理整个公司'),
	(3,'校长','管理整个学校');

用户角色中间表:

CREATE TABLE `user_role` (
  `UID` int(11) NOT NULL COMMENT '用户编号',
  `RID` int(11) NOT NULL COMMENT '角色编号',
  PRIMARY KEY  (`UID`,`RID`),
  KEY `FK_Reference_10` (`RID`),
  CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
  CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;	

insert  into 
	`user_role`(`UID`,`RID`) 
values 
	(41,1),(45,1),(41,2);

业务要求及实现SQL

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

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

下面是实现的SQL语句:

select 
	r.*, 
	u.id as uid, 
	u.username, 
	u.address, 
	u.sex, 
	u.birthday 
from 
	role r 
left join 
	user_role ur 
on (ur.rid=r.id)
left join 
	user u 
on (ur.uid=u.id);

step1: 编写角色实体类

package com.uestc.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 setUsers(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 + '\'' +
                '}';
    }
}

step2:编写Role持久层接口

package com.uestc.dao;
import com.uestc.domain.Role;
import java.util.List;
public interface IRoleDao  {
    /**
     * 查询所有角色
     * @return
     */
    List<Role> findAll();
}

step3: 编写映射文件

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

step4: 编写测试类

package com.uestc.test;
public class RoleTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IRoleDao roleDao;
    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        System.out.println(sqlSession);
        //5.使用sqlSession创建IUserdao的代理对象
        roleDao=sqlSession.getMapper(IRoleDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试查询所有
     */
    @Test
    public void TestFindAll(){
        List<Role> roles=roleDao.findAll();
        for (Role role: roles ) {
            System.out.println("----每个角色赋予的用户信息");
            System.out.println(role);
            System.out.println(role.getUsers());
        }
    }
}

12.2 实现User到Role的多对多

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

step1: 业务要求及实现SQL

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

step2 :编写用户实体类

package com.uestc.domain;
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<User> roles;
    public List<User> getRoles() {
        return roles;
    }

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

    public User(){};

    public User(Integer id, String username, String address, String sex, Date birthday) {
        this.id = id;
        this.username = username;
        this.address = address;
        this.sex = sex;
        this.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;
    }

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

step3:编写用户持久层接口

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有,同时获取用户下所有的账户信息
     * @return
     */
    List<User> findAll();

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

step4: 编写映射文件

<?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.uestc.dao.IUserDao">
    <!--定义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 join user_role ur on u.id=ur.uid
        left join role r on ur.rid=r.id;
    </select>
</mapper>

step5: 编写测试类

package com.uestc.test;
public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        System.out.println(sqlSession);
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    @Test
    public void TestFindAll(){
        List<User> users=userDao.findAll();
        for (User user: users ) {
            System.out.println("----每个用户的角色信息");
            System.out.println(user);
            System.out.println(user.getRoles());
        }
    }
}

13. JNDI数据源

JNDI:Java Naming and Directory Interface。是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表。

步骤

step1: 创建Maven的war工程并导入坐标

注意java, resouces文件需要自己创建,并make source

<?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>com.uestc</groupId>
  <artifactId>day03jndimybatis</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>day03jndimybatis Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>

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

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>day03jndimybatis</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

step2: 在webapp文件下创建META-INF目录

step3: 在META-INF目录中建立一个名为context.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- 
<Resource 
name="jdbc/eesy_mybatis"						数据源的名称
type="javax.sql.DataSource"						数据源类型
auth="Container"								数据源提供者
maxActive="20"									最大活动数
maxWait="10000"									最大等待时间
maxIdle="5"										最大空闲数
username="root"									用户名
password="1234"									密码
driverClassName="com.mysql.jdbc.Driver"			驱动类
url="jdbc:mysql://localhost:3306/eesy_mybatis"	连接url字符串
/>
 -->
<Resource 
name="jdbc/eesy_mybatis"
type="javax.sql.DataSource"
auth="Container"
maxActive="20"
maxWait="10000"
maxIdle="5"
username="root"
password="287216"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mybatis"
/>
</Context>

step4: 修改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">
<configuration>
    <typeAliases>
        <package name="com.uestc.domain"></package>
    </typeAliases>
    <!-- 配置mybatis的环境 -->
    <environments default="mysql">
        <!-- 配置mysql的环境 -->
        <environment id="mysql">
            <!-- 配置事务控制的方式 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的必备信息  type属性表示是否使用数据源(连接池)-->
            <dataSource type="JNDI">
                <property name="data_source" value="java:comp/env/jdbc/eesy_mybatis"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定mapper配置文件的位置 -->
    <mappers>
        <mapper resource="com/uestc/dao/IUserDao.xml"/>
    </mappers>
</configuration>

step5:新建index.jsp

<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.ibatis.io.Resources" %>
<%@ page import="org.apache.ibatis.session.SqlSessionFactoryBuilder" %>
<%@ page import="org.apache.ibatis.session.SqlSessionFactory" %>
<%@ page import="org.apache.ibatis.session.SqlSession" %>
<%@ page import="com.uestc.dao.IUserDao" %>
<%@ page import="com.uestc.domain.User" %>
<%@ page import="java.util.List" %>
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<body>
<h2>helloword</h2>
<%
    InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
    SqlSessionFactory factory=builder.build(is);
    SqlSession sqlSession=factory.openSession();
    IUserDao userDao=sqlSession.getMapper(IUserDao.class);
    List<User> users=userDao.findAll();
    for(User user:users){
        System.out.println(user);
    }
    sqlSession.close();
    is.close();
%>
</body>

</html>

step6: 部署启动tomcat

14. Mybatis延迟加载策略

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

  1. 在查询用户的时候,要不要把关联的账户信息查出来?

  2. 在查询账户的时候,要不要把关联的用户信息查出来?

  1. 在查询用户的时候,用户下的账户信息应该:什么时候使用,就什么时候查询
  2. 在查询账户的时候,账户下的所属用户信息应该时随着账户查询时一起查询出来

14.1 什么是延迟加载?

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

14.2 什么是立即加载

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

14.3 选择策略

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

  • 多对一和一对一:通常情况下我们都是采用立即加载

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

实现需求:查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。

14.4 使用assocation实现延迟加载

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

step1: 账户的持久层DAO接口

package com.uestc.dao;
import com.uestc.domain.Account;
import java.util.List;
public interface IAccountDao {
    /**
     * 查询所有账户,同时还要获取到当前账户的所属的用户信息
     * @return
     */
    List<Account> findAll();
}

step2: 账户的持久层映射文件

<?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.uestc.dao.IAccountDao">
    <!--定义Account的resultMap-->
    <resultMap id="accountmap" type="account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!--配置account对象中的users集合的映射
            select属性指定的内容:查询用户的唯一标识
            column属性指定的内容,用户根据id查询时,所需要的参数的值
        -->
        <association property="user" javaType="user" column="uid" select="com.uestc.dao.IUserDao.findById">
        </association>
    </resultMap>
    <!--查询所有-->
    <select id="findAll" resultMap="accountmap">
        select * from account
    </select>

</mapper>

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

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;

/**
 * 用户的持久层接口
 */
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.uestc.dao.IUserDao">
    <select id="findById" parameterType="int" resultType="user">
        select * from user where id= #{id}
    </select>
</mapper>

step4: 开启Mybatis的延迟加载策略

我们需要在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">

<configuration>
    <properties resource="pro.properties"></properties>
    <!--配置参数-->
    <settings>
        <!-- 开启延迟加载,默认值是true -->
        <setting name="lazyLoadingEnabled" value="true" />
        <!-- 设置积极懒加载,默认值是true -->
        <setting name="aggressiveLazyLoading" value="false"/>  
    </settings>
    <typeAliases>
        <package name="com.uestc.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.uestc.dao"></package>

    </mappers>
</configuration>

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

package com.uestc.test;
public class AccountTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IAccountDao accountDao;
    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        System.out.println(sqlSession);
        //5.使用sqlSession创建IUserdao的代理对象
        accountDao=sqlSession.getMapper(IAccountDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }

    @Test
    public void TestFindAll(){
        List<Account> accounts =accountDao.findAll();

    }

}

未开启延迟加载

开启了延迟加载

14.5 使用Collection实现延迟加载

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

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

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

package com.uestc.domain;
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;
    }

    public User(){};

    public User(Integer id, String username, String address, String sex, Date birthday) {
        this.id = id;
        this.username = username;
        this.address = address;
        this.sex = sex;
        this.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;
    }

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

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

用户持久层

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;

/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 查询所有,同时获取用户下所有的账户信息
     * @return
     */
    List<User> findAll();
}

账户持久层接口

package com.uestc.dao;
import com.uestc.domain.Account;
import java.util.List;
public interface IAccountDao {
    /**
     * 根据uid查询账户
     * @param uid
     * @return
     */
    List<Account> findByUid(int uid);
}

step3: 编写用户持久层映射配置

<?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.uestc.dao.IUserDao">
    <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是用于建立一对多中集合属性的对应关系
         ofType用于指定集合元素的数据类型 
         select是用于指定查询账户的唯一标识(账户的dao全限定类名加上方法名称) 
         column是用于指定使用哪个字段的值作为条件查询 -->
        <collection property="accounts" ofType="account" select="com.uestc.dao.IAccountDao.findByUid" column="id">
        </collection>
    </resultMap>
    <!--查询所有-->
    <select id="findAll" resultMap="usermap">
        select * from user
    </select>
</mapper>

<collection>标签: 主要用于加载关联的集合对象

  • select属性:

    用于指定查询account列表的sql语句,所以填写的是该sql映射的id

  • column属性:

    用于指定select属性的sql语句的参数来源,上面的参数来自于user的id列,所以就写成id这一个字段名了

step4:编写账户持久层映射配置

<?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.uestc.dao.IAccountDao">
    <!--根据uid查询账户-->
    <select id="findByUid" parameterType="int" resultType="account">
        select * from account where uid =#{uid}
    </select>
</mapper>

step5:测试只加载用户信息

package com.uestc.test;
public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        System.out.println(sqlSession);
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试findALL
     */
    @Test
    public void TestFindAll(){
        List<User> users=userDao.findAll();
    }
}

未开启延迟加载

开启延迟加载

15. Mybatis缓存

15.1 什么是缓存?

存在于内存中的临时数据

15.2 为什么使用缓存?

减少和数据库的交互次数,提高执行效率

15.3 什么样的数据适用于缓存?

  1. 检查查询并且不经常改变的

  2. 数据的正确与否对最终结果影响不大的

而经常改变的数据,且数据的正确与否对最终结果影响很大的,例如:商品的库存、银行的汇率和股市的牌价

15.4 Mybatis一级缓存

一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。

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

当SqlSession对象消失是,mybatis的一级缓存也就消失了

15.41 证明一级缓存的存在

step1. 编写用户持久层Dao接口

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 根据id查询
     * @param userId
     * @return
     */
    User findById(Integer userId);
}

step2. 编写用户持久层映射文件

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

step3. 编写测试方法

package com.uestc.test;
public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        SqlSessionFactory factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        System.out.println(sqlSession);
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    
    @Test
    public void TestFindCache(){
        User user1=userDao.findById(41);
        System.out.println(user1);
        User user2=userDao.findById(41);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

}

我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为41的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。

15.42 一级缓存的分析

一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

  1. 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
  2. 得到用户信息,将用户信息存储到一级缓存中。
  3. 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
  4. 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
15.43 测试一级缓存的清空
package com.uestc.test;
public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;
    private SqlSessionFactory factory;
    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        //7.释放资源
        sqlSession.close();
        in.close();
    }
    @Test
    public void TestFindCache(){
        User user1=userDao.findById(41);
        System.out.println(user1);
        //sqlSession.close();
        //再次获取SqlSession对象
        //sqlSession=factory.openSession();
        //userDao=sqlSession.getMapper(IUserDao.class);
        sqlSession.clearCache();//此方法也可以清空缓存
        User user2=userDao.findById(41);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

}


当执行sqlSession.close()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql 语句,从数据库进行了查询操作。

15.43 测试缓存同步

step1. 编写dao接口

package com.uestc.dao;
import com.uestc.domain.User;
import java.util.List;
/**
 * 用户的持久层接口
 */
public interface IUserDao {
    /**
     * 根据id查询
     * @param userId
     * @return
     */
    User findById(Integer userId);
    /**
     * 更新用户
     * @param user
     */
    void updateUser(User user);
}

step2. 表写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">
<mapper namespace="com.uestc.dao.IUserDao">
    <!-- 查询所有-->
    <select id="findAll" resultType="user">
       select * from user
    </select>

    <!-- 根据id查询-->
    <select id="findById" parameterType="int" resultType="user">
        select * from user where id=#{id}
    </select>

    <!--更新用户-->
    <select id="updateUser" parameterType="User">
        update user set username=#{username}, address=#{address} where id=#{id}
    </select>
</mapper>

step3. 编写测试

package com.uestc.test;
public class UserTest {
    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;
    private SqlSessionFactory factory;
    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        factory=builder.build(in);
        //4.使用factory创建sqlSession对象
        sqlSession=factory.openSession();
        //5.使用sqlSession创建IUserdao的代理对象
        userDao=sqlSession.getMapper(IUserDao.class);
    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{

        //7.释放资源
        sqlSession.close();
        in.close();
    }
    @Test
    public void TestClearCache(){
        User user1=userDao.findById(41);
        System.out.println(user1);

        user1.setUsername("update clear cache");
        user1.setAddress("北京海淀区");
        userDao.updateUser(user1);
        sqlSession.commit();
        User user2=userDao.findById(41);
        System.out.println(user2);
        System.out.println(user1==user2);
    }
}

15.5 Mybatis二级缓存

它指的是Mybatis中SqlSessionFactory对象的缓存,由用一个SqlSessionFactory对象创建的SqlSession共享其缓存。

二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

15.51 二级缓存结构图
  • 首先开启mybatis的二级缓存。
  • sqlSession1去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
  • 如果SqlSession3去执行相同 mapper映射下sql,执行commit提交,将会清空该 mapper映射下的二级缓存区域的数据。
  • sqlSession2去查询与sqlSession1相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据
15.52 二级缓存的开启与关闭

步骤:

第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)

第一步:在SqlMapConfig.xml文件开启二级缓存

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

因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。

第二步:配置相关的Mapper映射文件

<?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.uestc.dao.IUserDao">
    <!--开启user支持二级缓存-->
    <cache></cache>
    <!-- 查询所有-->
    <select id="findAll" resultType="user">
       select * from user
    </select>

    <!-- 根据id查询-->
    <select id="findById" parameterType="int" resultType="user" useCache="true">
        select * from user where id=#{id}
    </select>

    <!--更新用户-->
    <select id="updateUser" parameterType="User">
        update user set username=#{username}, address=#{address} where id=#{id}
    </select>
</mapper>

第三步:配置statement上面的useCache属性

<!-- 根据id查询-->
    <select id="findById" parameterType="int" resultType="user" useCache="true">
        select * from user where id=#{id}
    </select>

UserDao.xml映射文件中的<select>标签中设置useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。 注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。

第四步:二级缓存测试

package com.uestc.test;
public class SecondLevelCached {
    private InputStream in;
    private SqlSessionFactory factory;
    @Before //用于在测试方法执行之前执行
    public void init() throws Exception{
        //1.读取配置文件,生成字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建构建者
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        //3.使用构建者创建SqlSessionFactory
        factory=builder.build(in);

    }
    @After  //用于在测试方法执行之后执行
    public void destory()throws Exception{
        in.close();
    }

    @Test
    public void TestClearCache(){
        SqlSession sqlSession1= factory.openSession();
        IUserDao userDao1=sqlSession1.getMapper(IUserDao.class);
        User user1=userDao1.findById(41);
        System.out.println(user1);
        sqlSession1.close();

        SqlSession sqlSession2= factory.openSession();
        IUserDao userDao2=sqlSession2.getMapper(IUserDao.class);
        User user2=userDao2.findById(41);
        System.out.println(user2);

        sqlSession1.close();
        System.out.println(user1==user2);
    }
}

经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出sql语句,所以此时的数据就只能是来自于我们所说的二级缓存。

为啥是false呢?因为二级缓存存入的不是对象而是数据

16. Mybatis注解开发

  1. 环境搭建
  2. 单标CRUD操作(代理Dao方法)
  3. 多表查询操作
  4. 缓存的配置

16.1 mybatis的常用注解说明

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

代替的是标签<resultMap>

该注解中可以使用单个@Result注解,也可以使用@Result集合 @Results({@Result(),@Result()})@Results(@Result())

2、@Resutl注解

代替了 <id>标签和<result>标签

@Result 中 属性介绍:

  • id 是否是主键字段

  • column 数据库的列名

  • property需要装配的属性名

  • one 需要使用的@One注解(@Result(one=@One())

  • many 需要使用的@Many注解(@Result(many=@many()

3、@One注解(一对一)

代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

@One注解属性介绍

  • select指定用来多表查询的sqlmapper

  • fetchType会覆盖全局的配置参数lazyLoadingEnabled。。

使用格式

@Result(column=" ",property="",one=@One(select=""))

4、@Many注解(多对一)

代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。

注意:聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList)但是注解中可以不定义;

使用格式:

@Result(property="",column="",many=@Many(select=""))

16.2 使用Mybatis注解实现基本CRUD

step1. 编写实体类

注意: 此处我们故意和数据库表的列名不一致。

package com.uestc.domain;
import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date UserBirthday;

    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 String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return UserBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        UserBirthday = userBirthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAddress='" + userAddress + '\'' +
                ", userSex='" + userSex + '\'' +
                ", UserBirthday=" + UserBirthday +
                '}';
    }
}

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

通过注解方式,我们就不需要再去编写UserDao.xml映射文件了。

package com.uestc.dao;
public interface IUser {
    /**
     * 查询所有
     * @return
     */
    @Select("select * from user")
    @Results(id="usermap",
            value ={
            @Result(id =true, property ="userId",column="id"),
            @Result( property ="userName",column="username"),
            @Result( property ="userAddress",column="address"),
            @Result( property ="userSex",column="sex"),
            @Result( property ="userBirthday",column="birthday")
            }
    )
    List<User> findAll();


    /**
     * 根据id查询用户
     * @return
     */
    @Select("select * from user where id=#{id}")
    @ResultMap("usermap")
    User findById(Integer uid);


    /**
     * 保存用户
     * @param usr
     */
    @Insert("insert into user(username, address,sex, birthday) values(#{userName},#{userAddress}, #{userSex},#{userBirthday})")
    void saveUser(User usr);


    /**
     * 更新用户
     * @param user
     */

    @Update("update user set username=#{userName}, address=#{userAddress} where id=#{userId}")
    void updateUser(User user);

    /**
     * 删除用户
     * @param uid
     */
    @Delete("delete from user where id=#{uid}")
    void deleteUser(Integer uid);


    /**
     * 聚合函数count
     */
    @Select("select count(id) from user")
    int findTotol();

    /**
     * 模糊查询
     * @return
     */
    @Select("select * from user where username like #{name} ")
    @ResultMap("usermap")
    List<User> findByName(String name);

}

step3. 编写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="pro.properties"></properties>
        <!-- 配置别名的注册 -->
        <typeAliases>
            <package name="com.uestc.domain"></package>
        </typeAliases>
        <!-- 配置环境 -->
        <environments default="mysql">
            <!-- 配置mysql的环境 -->
            <environment id="mysql">
                <!-- 配置事务的类型是JDBC -->
                <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.uestc.dao"></package>
        </mappers>

</configuration>

step4. 编写测试方法

package com.uestc.test;
public class TestAnnotation {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IUser userdao;
    @Before
    public void init() throws Exception{


        //1.获取字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取factory
        factory= new SqlSessionFactoryBuilder().build(in);
        //3.获取sqlSession对象
        sqlSession=factory.openSession();
        //4.获取dao代理对象
        userdao=sqlSession.getMapper(IUser.class);

    }
    @After
    public void destory() throws Exception{
        sqlSession.commit();
        sqlSession.close();
        in.close();

    }

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


    /**
     * 测试findById
     */
    @Test
    public void testFindById(){
        User user=userdao.findById(41);
        System.out.println(user);

    }
    /**
     * 测试saveUser
     */
    @Test
    public void testSave(){
        User user=new User();
        user.setUserName("快手");
        user.setUserSex("男");
        user.setUserAddress("北京快手总部");
        user.setUserBirthday(new Date());
        userdao.saveUser(user);

    }

    /**
     * 测试updateUser方法
     */
    @Test
    public void testUpdateUser(){
        User user=userdao.findById(42);
        user.setUserName("超女");
        user.setUserSex("女");
        userdao.updateUser(user);

    }

    /**
     * 测试deleteUser方法
     */
    @Test
    public void testDeleteUser(){

        userdao.deleteUser(41);

    }

    /**
     * 测试聚合函数
     */
    @Test
    public void TestFindTotol(){
        System.out.println(userdao.findTotol());
    }

    /**
     * 测试模糊查询
     */
    @Test
    public void TestFindByName(){
        //执行查找功能
        List<User> users=userdao.findByName("%王%");
        for(User user:users){
            System.out.println(user);
        }
    }

}

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

16.31 使用注解实现一对一复杂关系映射及延迟加载

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

step1. 添加User实体类及Account实体类

User实体类

package com.uestc.domain;
import java.io.Serializable;
import java.util.Date;


public class User implements Serializable {
    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date UserBirthday;

    
    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 String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return UserBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        UserBirthday = userBirthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAddress='" + userAddress + '\'' +
                ", userSex='" + userSex + '\'' +
                ", UserBirthday=" + UserBirthday +
                '}';
    }
}

Account实体类

package com.uestc.domain;

public class Account {
    private Integer id;
    private Integer uid;
    private Double money;

    //一对一的关系,添加user
    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 +
                '}';
    }
}

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

package com.uestc.dao;
public interface IAccount {
    /**
     * 查询所有账户,采用延迟加载的方式查询账户的所属用户
     */
    @Select({"select * from account"})
    @Results(id = "accountmap",
        value= {
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "uid", column = "uid"),
            @Result(property = "money", column = "money"),
            @Result(property = "user",
                    column = "uid",
                    one=@One(select = "com.uestc.dao.IUser.findById",
                            fetchType= FetchType.EAGER)
            )
        }
    )
    List<Account> findAll();

}

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

package com.uestc.dao;
public interface IUser {
    /**
     * 查询所有
     * @return
     */
    @Select("select * from user")
    @Results(id="usermap",
            value ={
            @Result(id =true, property ="userId",column="id"),
            @Result( property ="userName",column="username"),
            @Result( property ="userAddress",column="address"),
            @Result( property ="userSex",column="sex"),
            @Result( property ="userBirthday",column="birthday")
            }
    )
    List<User> findAll();


    /**
     * 根据id查询用户
     * @return
     */
    @Select("select * from user where id=#{id}")
    @ResultMap("usermap")
    User findById(Integer uid);
}

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

package com.uestc.test;
public class TestAccount {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IAccount accountdao;

    @Before
    public void init() throws Exception{
        //1.获取字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取factory
        factory= new SqlSessionFactoryBuilder().build(in);
        //3.获取sqlSession对象
        sqlSession=factory.openSession();
        //4.获取dao代理对象
        accountdao=sqlSession.getMapper(IAccount.class);
    }
    @After
    public void destory() throws Exception{
        sqlSession.commit();
        sqlSession.close();
        in.close();
    }

    /**
     * 测试findAll
     */
    @Test
    public void testFindAll(){
        List<Account> accounts=accountdao.findAll();
//        for(Account account:accounts){
//            System.out.println("-----每个账户下的用户信息");
//            System.out.println(account);
//            System.out.println(account.getUser());
//        }
    }

}

16.32使用注解实现一对多复杂关系映射

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

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

step1. User实体类加入List<Account>

package com.uestc.domain;
public class User implements Serializable {
    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date UserBirthday;
    /**
     * 一对多关系,添加account集合
     */
    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 String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return UserBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        UserBirthday = userBirthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAddress='" + userAddress + '\'' +
                ", userSex='" + userSex + '\'' +
                ", UserBirthday=" + UserBirthday +
                '}';
    }
}

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

package com.uestc.dao;
import com.uestc.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import org.junit.Test;
import java.util.List;

public interface IUser {
    /**
     * 查询所有
     * @return
     */
    @Select("select * from user")
    @Results(id="usermap",
            value ={
            @Result(id =true, property ="userId",column="id"),
            @Result( property ="userName",column="username"),
            @Result( property ="userAddress",column="address"),
            @Result( property ="userSex",column="sex"),
            @Result( property ="userBirthday",column="birthday"),
            @Result(property = "accounts",
                    column = "id",
                    many = @Many(
                            select = "com.uestc.dao.IAccount.findById",
                            fetchType = FetchType.LAZY)
                          )
            }
    )
    List<User> findAll();
}

@Many: 相当于<collection>的配置

select属性:代表将要执行的sql语句

fetchType属性:代表加载方式,一般如果要延迟加载都设置为LAZY的值

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

 /**
   * 根据id查询账户
   * @return
   */
   @Select("select * from account where uid=#{id}")
   List<Account> findById(int uid);
}

step4. 添加测试方法

package com.uestc.test;
public class TestAnnotation {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IUser userdao;

    @Before
    public void init() throws Exception{


        //1.获取字节输入流
        in= Resources.getResourceAsStream("SqlMapConfig.xml");

        //2.获取factory
        factory= new SqlSessionFactoryBuilder().build(in);

        //3.获取sqlSession对象
        sqlSession=factory.openSession();

        //4.获取dao代理对象
        userdao=sqlSession.getMapper(IUser.class);



    }
    @After
    public void destory() throws Exception{
        sqlSession.commit();
        sqlSession.close();
        in.close();

    }

    /**
     * 测试findAll
     */
    @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());
        }
    }

}

16.4 mybatis基于注解的二级缓存

step1. 在SqlMapConfig中开启二级缓存支持

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

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

@CacheNamespace(blocking=true)//mybatis基于注解方式实现配置二级缓存 
public interface IUserDao {}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值