IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Mybatis -> 正文阅读

[Java知识库]Mybatis

Mybatis

框架概述

1、三层架构

  • 三层架构包含的三层:

    • 界面层(User Interface layer)
    • 业务逻辑层(Business Logic Layer)
    • 数据访问层(Data access layer)
  • 三层的职责

    • 界面层(表示层,视图层):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和用户交互,手机 app也就是表示层的,用户在app中操作,业务逻辑在服务器端处理。
    • 业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
    • 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库.
  • 三层对应的包

    • 界面层: controller包(servlet)
    • 业务逻辑层:service包(xxxService)
    • 薮据访问层:dao包( xxxDao类)
  • 三层中类的交互

    • 用户使用界面层–>业务逻辑层—>数据访问层(持久层)–>数据库(mysql)
  • 三层对应的处理框架

    • 界面层—servlet—springavc(框架)
    • 业务逻辑层—service类–spring(框架)
    • 薮据访问层—dao类–mybatis(框架)

2、什么是框架

  • 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;
  • 另一种定义认为,框架是可被应用开发者定制的应用骨架。
  • 前者是从应用方面而后者是从目的方面给出的定义。
  • 简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。

3、框架要解决的问题

  • 框架要解决的最重要的一个问题是技术整合的问题,在J2EE的 框架中,有着各种各样的技术,不同的软件企业需要从J2EE中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。

4、软件开发的分层重要性

  • 框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的MVC软件设计思想就是很好的分层思想。
    在这里插入图片描述

Mybatis

一、Mybatis的概述

1、MyBatis框架概述

  • mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
  • mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。

2、JDBC的缺陷

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

从上述代码可以看出:

  • 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
  • Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
  • 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  • 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

简单地说就是:

  • 代码比较多,开发效率低
  • 需要关注Connection ,Statement, ResultSet对象创建和销毁
  • 对 ResultSet查询的结果,需要自己封装为List
  • 重复的代码比较多些
  • 业务代码和数据库的操作混在一起

3、Mybatis解决的主要问题

  • mybatis提供了哪些功能:
    • 提供了创建connection ,statement,Resultset的能力
    • 提供了执行sql语句的能力
    • 提供了循环sql,把sql的结果转为java对象,List集合的能力
    • 提供了关闭资源的能力,不用你关闭connection,statement,Resultset
  • 总结:
    • 减轻使用JDBC的复杂性,不用编写重复的创建Connetion , Statement;不用编写关闭资源代码。直接使用java对象,表示结果数据。让开发者专注 SQL的处理。其他分心的工作由 MyBatis 代劳。

二、Mybatis入门

1、构建Mybatis项目

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
  • 入门案例使用Maven来构建项目,所以需要有Maven相关知识的基础
  • 关于如何创建Maven工程不是这里的重点,在学习Maven工程时会有教程

入门案例

  • 在Idea中新建一个Maven工程
    在这里插入图片描述
  • 在pom.xml中导入依赖坐标
	<!--mybatis依赖-->
	<dependency>
	 <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>
    <!--junit测试单元依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--数据库驱动依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.22</version>
    </dependency>
  • 创建相应的数据库
CREATE DATABASE `student`;

USE `student`;

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` int NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
)

insert  into `student`(`id`,`name`,`email`,`age`) values 
(1001,'张三','zhangsan@qq.com',20);
insert  into `student`(`id`,`name`,`email`,`age`) values 
(1002,'李四','lisi@qq.com',18);
  • 创建实体类,student
    在这里插入图片描述
package com.bin.domain;

public class Student {
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}

  • 创建持久层的dao接口,定义操作数据库的方法
    在这里插入图片描述
package com.bin.dao;

import com.bin.domain.Student;

import java.util.List;

public interface StudentDao {
    /**
     * 查询所有
     * @return  List<Student>
     */
    List<Student> findAll();
}
  • 创建一个mybatis使用的配置文件, 叫做sql映射文件:写sql语句的。一般一个表一个sql映射文件。
  • 这个文件的位置并不是固定的,而是我为了文件管理的方便放在resource文件夹下了,注意resource一定要标识为资源文件夹
    在这里插入图片描述
<!--
sql映射文件:写sql语句的,mybatis会执行这些sql
  1.指定约束文件
      <! DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

        mybatis-3-mapper.dtd是约束文件的名称,扩展名是dtd的。

  2.约束文件作用:限制,检查在当前文件中出现的标签,属性必须符合mybatis的要求。

  3.mapper是当前文件的根标签,必须的。
      namespace:叫做命名空间,唯一值的,可以是自定义的字符串。
                要求你使用dao接口的全限定名称。
  4.在当前文件中,可以使用特定的标签,表示数据库的特定操作。
    <select>:表示执行查询,select语句
    <update>:表示更新数据库的操作,就是在<update>标签中写的是update sql语句
    <insert>:表示插入,放的是insert语句
    <delete>:表示删除,执行的delete语句
-->

<?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.bin.dao.StudentDao">
   <!--
    select:表示查询操作。
        id:你要执行的sql语法的唯一标识, mybatis会使用这个id的值来找到要执行的sql语句
           可以自定义,但是要求你使用接口中的方法名称。
        resultType:表示结果类型的,是sql语句执行后得到ResultSet,遍历这个ResultSet得到java对象的类型。
                   值写的类型的全限定名称,这里表示结果集封装为Student对象的结果集
  -->
  <select id="findAll" resultType="com.bin.domain.Student">
    select * from Student
  </select>
</mapper>

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

  • 创建mybatis的主配置文件:
    一个项目就一个主配置文件。
    主配置文件提供了数据库的连接信息和sql映射文件的位置信息
    在这里插入图片描述
<!--        
mybatis的主配置文件:主要定义了数据库的配筐信息,sql映射文件的位置
        1.约束文件
           <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.e//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
           mybatis-3-config.dtd :约束文件的名称
        2. configuration根标签。
-->

<?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">
   <!--
    环境配置:数据库的连接信息
        default:必须和某个environment的id值一样。
                告诉mybatis使用哪个数据库的连接信息。也就是访问哪个数据库
  -->
    <environment id="mysql">
    <!--transactionManager : mybatis的事务类型
              type: JDBC(表示使用jdbc中的connection对象的commit , rollback做事务处理)
      -->
      <transactionManager type="JDBC" />
       <!--
        dataSource:表示数据源,连接数据库的
          type :表示数据源的类型,POOLED表示使用连接池
      -->
      <dataSource type="POOLED">
      <!--driver,user,username,password是固定的,不能自定义。-->
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///student?characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
<!-- sql mapper(sql映射文件)的位置-->
  <mappers>
   <!--
        一个mapper标签指定一个文件的位置。
        从类路径开始的路径信息。 target/Classes(类路径)
        mapper可以有多个,这里只有一个,而且放在资源目录下,
        可以直接以dao文件夹开始
    -->
    <mapper resource="dao/StudentDao.xml"/>
  </mappers>
</configuration>

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

  • 编写一个测试类,对其进行测试
    在这里插入图片描述
import com.bin.domain.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

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

public class test {
    @Test
    public void testFindAll() throws IOException {
        //1、读取主配置文件的配置
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.通过SqlSessionFactoryBuilder()创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //3.通过SqlSessionFactory创建SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //执行Sql语句,sql映射文件中的namespace + ".”+标签的id值
        String path = "com.bin.dao.StudentDao.findAll";
        List<Student> students = sqlSession.selectList(path);
        for (Student s :students) {
            System.out.println(s);
        }
        //关闭相应的资源
        inputStream.close();
        sqlSession.close();
    }
}

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

2、插入操作

  • 在主配置文件中配置日志,方便我们更好地对我们的操作进行解读
<!--settings :控制mybatis全局行为-->
  <settings>
    <!--设置mybatis输出日志-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
  </settings>

在这里插入图片描述

  • 在StudentDao中编写相应的方法
package com.bin.dao;

import com.bin.domain.Student;

import java.util.List;

public interface StudentDao {
    /**
     * 查询所有
     * @return  List<Student>
     */
    List<Student> findAll();

    /**
     * 插入操作
     * @param student Student对象
     */
    void insert(Student student);
}
  • 在mapper配置文件中加入相应的配置
 <insert id="insert">
    insert into Student(id,name,email,age) values (#{id},#{name},#{email},#{age})
 </insert>

在这里插入图片描述

  • 编写测试类对其进行测试
import com.bin.domain.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class test {
    InputStream inputStream;
    SqlSessionFactory sqlSessionFactory;
    SqlSession sqlSession;
    @Before
    public void init() throws IOException {
        //1、读取主配置文件的配置
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.通过SqlSessionFactoryBuilder()创建SqlSessionFactory对象
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //3.通过SqlSessionFactory创建SqlSession对象
        sqlSession = sqlSessionFactory.openSession();
    }
    @After
    public void destroy() throws IOException {
        //提交事务
        sqlSession.commit();
        //关闭相应的资源
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        //执行Sql语句,sql映射文件中的namespace + ".”+标签的id值
        String path = "com.bin.dao.StudentDao.findAll";
        List<Student> students = sqlSession.selectList(path);
        for (Student s :students) {
            System.out.println(s);
        }

    }
    @Test
    public void testInsert(){
        Student student = new Student();
        student.setId(1003);
        student.setName("王五");
        student.setEmail("wangwu@qq.com");
        student.setAge(20);
        String path = "com.bin.dao.StudentDao.insert";
        sqlSession.insert(path,student);
    }
}

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

3、更新操作

  • 在StudentDao中编写相应的方法
/**
     * 更新操作
     * @param student
     */
    void update(Student student);
  • 在mapper配置文件中加入相应的配置
<update id="update">
    update Student set name = #{name},email = #{email},age = #{age} where id = #{id}
  </update>
  • 编写测试类对其进行测试
 @Test
    public void testUpdate(){
        Student student = new Student();
        student.setId(1003);
        student.setName("李五");
        student.setEmail("liwu@qq.com");
        student.setAge(20);
        studentDao.update(student);
    }

4、删除操作

  • 在StudentDao中编写相应的方法
 /**
     * 删除操作
     * @param id
     */
    void delete(Integer id);
  • 在mapper配置文件中加入相应的配置
<delete id="delete">
    delete from Student where id = #{id}
  </delete>
  • 编写测试类对其进行测试
 @Test
    public void testDelete(){
        studentDao.delete(1003);
    }

5、主要的类的介绍

  • Resources: mybatis中的一个类,负贲读取主配置文件
    • InputStream in=Resources.getResourceAsStream(“SqlMapConfig.xml”);
  • SqlSessionFactoryBuilder :创建SqlSessionFactory对象,
    • SqlSessionFactory Factory = new SqlSessionFactoryBuilder().build(inputStream);
  • SqlSessionFactory :重量级对象,程序创建一个对象耗时比较长,使用资源比较多。在整个项目中,有一个就够用了.
    • SqlSessionFactory:接口,接口实现类:DefaultSqlSessionFactory
    • SqlSessionFactory作用:获取SqlSession对象
      SqlSession session = sqlSessionFactory.openSession();
      openSession ()方法说明:
      1. openSession() :无参数的,获取是非自动提交事务的SqlSession对象
      2. openSession(boolean): openSession(true)获取自动提交事务的SqlSession. openSession (false)非自动提交事务的SqlSession对象
  • SqlSession :
    • SqlSession接口︰定义了操作数据的方法
      • 例如SelectOne() ,SelectList() ,insert() ,update () , delete() , commit() , rollback()
    • SqlSession接口的实现类DefaultSqlSession.
      • 使用要求: sqlSession对象不是线程安全的,需要在方法内部使用,在执行sql语句之前,使用openSession()获取SqlSession在执行完sql语句后,需要关闭它,执行sqlSession.close().这样能保证他的使用是线程安全的。

三、Mybatis的Dao代理

1、动态代理getMapper

  • 通过上面的代码我们可以发现,其实StudentDao接口我们一直都是没有使用的,我们是直接通过SqlSession的方法直接使用了mapper配置文件来进行查询与插入操作的。
  • 然而我们在实际的开发时,包括最开始使用Servlet开发时,按照三层架构的思想来说,我们是通过创建对应接口的实现类,通过在方法中定义相应的Sql语句来进行相应的查询和插入操作的
  • 通过学习了Mybatis入门案例后,我们知道,Sql语句可以将其抽取出来放在配置文件中,那么此时的实现类将不再是最开始这样写了。
  • 借助在测试类test介绍的主要的相关类,我们就可以将获取这些类的方法封装成为一个工具类,通过工具类获取SqlSession对象,在Dao的实现类中定义相应的方法通过获取相应的SqlSession对象调用其方法执行配置文件中的sql语句。
  • 此时在测试类中,我们就可以通过创建对应的实现了对象,通过这个对象调用实现类中的方法执行相应的操作了
  • 但是,在此时,通过分析Dao的实现类,我们发现这样的操作让仍旧是存在这很多重复的工作的,比如每个方法都要写获取对应的SqlSession对象这一部分,除了最核心的执行部分不相同外,其他的操作基本大同小异,所以,这些工作应该是交给框架来做的
  • SqlSession中的getMapper方法就帮我们做了这些工作了
//通过getMapper方法获取studentDao接口的实现类对象,此时我们不需要去写studentDao接口的实现类了
Student studentDao = sqlSession.getMapper(StudentDao.class);

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

  • List<student> studentList = dao. findAll();
  • 调用
    • dao对象,类型是studentDao,全限定名称是:com. bin.dao.studentDao全限定名称和namespace 是一样的。
    • 方法名称,findAll ,这个方法就是mapper文件中的 id值 findAll
    • 通过dao中方法的返回值也可以确定MyBatis要调用的sqlsession的方法
      • 如果返回值是List,调用的是SqlSession.selectList()方法。
      • 如果返回值int ,或是非list的,看mapper文件中的标签是<insert> , <update>就会调用SqlSession的insert , update等方法
  • mybatis的动态代理:
    • mybatis根据dao的方法调用,获取执行sqL语句的信息。
    • mybatis根据你的dao接口,创建出一个dao接口的实现类,并创建这个类的对象。完成sqLSession调用方法,访问数据库。

2、parameterType配置参数

  • SQL语句传参,使用标签的parameterType属性来设定。该属性的取值可以是基本类型,引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类。
  • parameterType :写在mapper文件中的一个属性。表示dao接口中方法的参数的数据类型。
    • 例如studentDao接口
      public student findById(Integer id)

1)传入一个简单参数

  • 一个简单类型的参数:
    • 简单类型:mybatis把java的基本数据类型和string都叫简单类型。
    • 在mapper文件获取简单类型的一个参数的值,使用#{任意字符}
  • 在StudentDao增加一个findById的方法
package com.bin.dao;

import com.bin.domain.Student;

import java.util.List;

public interface StudentDao {
    /**
     * 查询所有
     * @return  List<Student>
     */
    List<Student> findAll();

    /**
     * 插入操作
     * @param student Student对象
     */
    void insert(Student student);

    /**
     * 通过id查找
     * @param id
     */
    Student findById(Integer id);
}

  • 在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.bin.dao.StudentDao">
  <select id="findAll" resultType="com.bin.domain.Student">
    select * from Student
  </select>
  <insert id="insert">
    insert into Student(id,name,email,age) values (#{id},#{name},#{email},#{age})
  </insert>

  <select id="findById" parameterType="java.lang.Integer" resultType="com.bin.domain.Student">
    select * from Student where id = #{id}
  </select>
</mapper>

在这里插入图片描述

  • 注意事项,关于paramteType的别名
    • 基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。
    • 实体类类型,目前我们只能使用全限定类名。 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。
    • 在Mybatis文档中可以查询得到可以写别名的类型
      在这里插入图片描述
  • 在测试类中写相应的测试方法
 @Test
    public void testFindById(){
        System.out.println(studentDao.findById(1001));
    }

在这里插入图片描述

  • 注意: parameterType不是强制的,mybatis通过反射机制能够发现接口参数的数类型。所以可以没有。一般我们也不写。

2)传入多个参数-使用@Param

  • 当Dao接口方法多个参数,需要通过名称使用参数。在方法形参前面加入@Param(“自定义参数名"),mapper文件使用#{自定义参数名}。
  • Dao文件
    • List<Student> selectStudent(@Param(“personName”) String name ) { …}
  • mapper文件
    • select* from student where name = #{ personName}
  • 在StudentDao中添加selectStudent方法
   /**
     * 多个参数查询
     * @param id
     * @param age
     * @return
     */
    List<Student> selectMultiParam(@Param("sid") Integer id,
                                @Param("sage") Integer age);
}
  • 在mapper文件中配置
 <select id="selectMultiParam" resultType="com.bin.domain.Student">
    select * from Student where id > #{sid} or age = #{sage}
 </select>

在这里插入图片描述

  • 在测试类中测试
    @Test
    public void testSelectStudent(){
        List<Student> lists = studentDao.selectMultiParam(1002,20);
        for (Student student : lists){
            System.out.println(student);
        }
    }
}

在这里插入图片描述

3)传入多个参数-使用对象

  • 使用java对象传递参数,java对象的属性值就是sql需要的参数值。每一个属性就是一个参数。
  • 语法格式:#{ property,javaType=java中数据类型名,jdbcType=数据类型名称}
  • javaType, jdbcType 的类型MyBatis可以检测出来,一般不需要设置。
  • 常用格式#{ property }
  • 在StudentDao中加入对应的方法
/**
     * 参数为对象
     * @param student
     * @return
     */
    List<Student> selectMultiObject(Student student);
  • 在mapper中加入配置

  <!--
      多个参数,使用java对象的属性值,作为参数实际值
      使用对象语法:
        #{属性名,javaType=类型名称,jdbcType=数据类型}很少用。
        javaType:指java中的属性数据类型。
        jdbcType:在数据库中的数据类型。
          例如:#{ id,javaType=java.Integer.string,jdbcType=INTEGER}

      我们使用的简化方式:#{属性名},javaType,jdbcType的值mybatis反射能获取。不用提供

-->
  <select id="selectMultiObject" resultType="com.bin.domain.Student">
    select * from Student where id > #{id} or age = #{age}
    <!--id >#{ id,javaType=java.lang.Integer,jdbcType=INTEGER}
   or age = #{ age,javaType=java.lang.Integer,jdbcType=INTEGER}-->
 </select>

在这里插入图片描述

  • 在test方法中加入测试
 @Test
    public void selectMultiObject(){
        Student student = new Student();
        student.setId(1002);
        student.setAge(20);
        List<Student> lists = studentDao.selectMultiObject(student);
        for (Student s : lists){
            System.out.println(s);
        }
    }

在这里插入图片描述

4)传入多个参数-按位置传入(了解)

  • 参数位置从0开始,引用参数语法#arg{位置},第一个参数是#{arg0},第二个是#{arg1}
  • 注意: mybatis-3.3版本和之前的版本使用#{0},#{1}方式,从mybatis3.4开始使用#{arg0}方式。
  • 在StudentDao中加入对应的方法
/**
     * 按位置传值
     * @param id
     * @param age
     * @return
     */
    List<Student> selectMultiPosition( Integer id, Integer age);
  • 在mapper中加入配置
  <select id="selectMultiPosition" resultType="com.bin.domain.Student">
    select * from Student where id > #{arg0} or age = #{arg1}
  </select>

在这里插入图片描述

  • 在测试类中加入测试
  @Test
    public void selectMultiPosition(){
        List<Student> lists = studentDao.selectMultiPosition(1002,20);
        for (Student student : lists){
            System.out.println(student);
        }
    }

在这里插入图片描述

5)传入多个参数-使用Map(了解)

  • Map集合可以存储多个值,使用Map向mapper文件一次传入多个参数.
  • Map集合使用String的 key,Object类型的值存储参数。
  • mapper文件使用# { key }引用参数值。
  • 在StudentDao中加入相应的方法
/**
     * 使用map传值
     * @param map
     * @return
     */
    List<Student> selectMultiMap(Map<String,Object> map);
  • 在mapper中加入配置
<select id="selectMultiMap" resultType="com.bin.domain.Student">
    select * from Student where id > #{myid} or age = #{myage}
  </select>

在这里插入图片描述

  • 在测试类中加入测试
@Test
    public void selectMultiMap(){
        Map<String,Object> map = new HashMap<>();
        map.put("myid",1002);
        map.put("myage",20);
        List<Student> lists = studentDao.selectMultiMap(map);
        for (Student student : lists){
            System.out.println(student);
        }
    }

在这里插入图片描述

6)#和$两个占位符的比较

  • #:占位符,告诉mybatis使用实际的参数值代替。并使用PrepareStatement对象执行sql语句, #{…}代替sql语句的“?”。这样做更安全,更迅速,通常也是首选做法
    在这里插入图片描述
  • $字符串替换,告诉mybatis使用$包含的“字符串”替换所在位置。使用Statement 把 sql语句和${…}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。
    在这里插入图片描述
  • 使用$替换列名
  • 在StudentDao中加入对应的方法
 /**
     * 查询所有并排序
     * @return  List<Student>
     */
    List<Student> findAllOrder(@Param("colName") String colName);
  • 在mapper中配置
<select id="findAllOrder" resultType="com.bin.domain.Student">
    select * from Student order by ${colName}
</select>

在这里插入图片描述

  • 在测试类中加入测试
  @Test
    public void testFindAllOrder(){
        List<Student> students = studentDao.findAllOrder("age");
        for (Student s :students) {
            System.out.println(s);
        }

    }

在这里插入图片描述

  • 总结:#和$区别
    • #使用?在sql语句中做占位的,使用preparedstatement执行sql,效率高
    • #能够避免sql注入,更安全
    • $不使用占位符,是字符串连接方式,使用statement对象执行sql,效率低
    • $有sql注入的风险,缺乏安全性
    • $ :可以替换表名或者列名

3、resultType配置结果类型

  • resultType属性可以指定结果集的类型,它支持基本类型和实体类类型。
  • 我们在前面的案例中已经对此属性进行过应用了。
  • 需要注意的是,它和parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。
  • 例如:我们的实体类此时必须是全限定类名, 同时,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

1)返回的结果是对象类型

  • 以最开始的查询全部学生信息为例
    在这里插入图片描述

2)返回的结果是简单类型

  • 在StudentDao中加入对应的方法
   /**
     * 查询表中的数据个数
     * @return int
     */
    int count();

  • 在mapper配置文件中加入配置
<select id="count" resultType="java.lang.Integer">
    select count(*) from Student
  </select>

在这里插入图片描述

  • 在测试类中加入测试方法
 @Test
    public void testCount(){
        int count =  studentDao.count();
        System.out.println(count);
    }

在这里插入图片描述

3)返回的结果是Map类型

  • 在StudentDao中加入相应的方法
/**
     * 返回Map
     * @param id
     * @return
     */
    Map<Object,Object> map(Integer id);
  • 在mapper配置文件中加入配置
<!--返回Map
    1)列名是map的key ,列值是map的value
    2)只能最多返回一行记录。多余一行是错误

  -->
  <select id="map" resultType="java.util.Map">
    select * from Student where id = #{id}
  </select>

在这里插入图片描述

  • 在测试类中加入测试方法
   @Test
    public void testMap(){
        Map<Object,Object> map = studentDao.map(1001);
        System.out.println(map);
    }

在这里插入图片描述

4、定义别名

  • 定义自定义类型的别名
    • 1、在mybati主配置文件中定义,使<\typeAlias>定义别名
    • 2、可以在resultType中使用自定义别名
  • 定义别名有两种方式
  • 第一种方式
<!--定义别名-->
  <typeAliases>
    <!--
            第一种方式
            可以指定一个类型一个自定义别名
            type:自定义类型的全限定名称
            alias:别名(短小,容易记忆的)
    -->
    <typeAlias type="com.bin.domain.student" alias="stu"/>
  </typeAliases>

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

  • 第二种方式
  <!--定义别名-->
  <typeAliases>
    <!--
        第二种方式
        <package> name是包名,这个包中的所有类,类名就是别名(类名不区分大小写)
    -->
    <package name="com.bin"/>
</typeAliases>

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

  • 第二种方式需要注意,如果包中存在同名的类将不能使用这种方式,这样无法找到具体的类,运行时会报错

5、resultMap结果类型

  • resultMap标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。 在select标签中使用resultMap属性指定引用即可。
  • 同时resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
  • 使用方式:
    • 1.先定义resultMap,指定列名和属性的对应关系。
    • 2.在.<select>中把resultType替换为resultMap
  • 在mapper配置文件中配置resultMap
 <!--定义resultMap
          id:自定义名称,表示你定义的这个resultMap
          type : java类型的全限定名称
  -->
    <resultMap id="StudentMap" type="com.bin.domain.Student">
      <!--
      主键列,使用id标签
             column:列名
             property:java类型的属性名
      -->
      <id property="id" column="id"/>
      <!--非主键列,使用result标签-->
      <result property="name" column="name"/>
      <result property="email" column="email"/>
      <result property="age" column="age"/>
    </resultMap>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
模拟实体类的属性名与列名不一样

  • 将Student类的属性名做出修改
package com.bin.domain;

public class Student {
    private Integer sid;
    private String sname;
    private String semail;
    private Integer sage;

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public String getSemail() {
        return semail;
    }

    public void setSemail(String semail) {
        this.semail = semail;
    }

    public Integer getSage() {
        return sage;
    }

    public void setSage(Integer sage) {
        this.sage = sage;
    }

    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                ", semail='" + semail + '\'' +
                ", sage=" + sage +
                '}';
    }
}

  • 在resultMap中做出配置
    <resultMap id="StudentMap" type="com.bin.domain.Student">
      <id property="sid" column="id"/>
      <result property="sname" column="name"/>
      <result property="semail" column="email"/>
      <result property="sage" column="age"/>
    </resultMap>

在这里插入图片描述

  • 第二种解决方案,可以不配做resultMap进行解决
  • 更改mapper中的配置,通过更改查询结果的列名,使其与实体类的属性名相同可以解决
<select id="findAll" resultType="Student" >
    select id as sid,name as sname,email as semail , age as sage from Student
</select>

在这里插入图片描述

  • 两种方式解决,各有利弊,具体选择哪一种,具体情况具体分析

6、模糊查询

  • 在StudentDao中加入相应的方法
    /**
     * 模糊查询
     * @param name
     * @return
     */
    List<Student> findByName(String name);
  • 在mapper配置文件中加入配置
<select id="findByName" resultMap="StudentMap">
    select * from Student where name like #{name}
  </select>
  • 在测试类中加入相关测试
 @Test
    public void testFindByName(){
        List<Student> students = studentDao.findByName("%李%");
        for (Student s :students) {
            System.out.println(s);
        }
    }

在这里插入图片描述

  • 模糊查询与一般的查询差别不大,但是有多种写法
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

四、动态SQL

  • 动态sql : sql的内容是变化的,可以根据条件获取到不同的sql语句
  • 主要是where部分发生变化

1、动态SQL之if标签

  • 对于该标签的执行,当test的值为true时,会将其包含的sQL片断拼接到其所在的sQL语句中。
  • 语法:<if test=“条件">sql语句的部分</if>
  • 重新新建一个Maven工程,除了主配置文件,其他代码保持与入门案例相同
  • 在StudentDao加入相应的方法
/**
     * 动态sql if
     * @param student
     * @return
     */
    List<Student> selectStudentIf(Student student);
  • 在mapper文件中加入配置
 <select id="selectStudentIf" resultType="Student">
    select * from Student where
    <if test="name != null and name!=''">
        name = #{name}
    </if>
    <if test="age > 0">
      or age > #{age}
    </if>
  </select>

在这里插入图片描述

  • 在测试类中加入测试
  @Test
    public  void testSelectStudentIf(){
        Student student = new Student();
        student.setName("李四");
        student.setAge(18);
        List<Student> students = studentDao.selectStudentIf(student);
        for (Student s :students) {
            System.out.println(s);
        }
    }

在这里插入图片描述

2、动态SQL之where标签

  • 分析上面的运行结果
    在这里插入图片描述
  • 在StudentDao加入相应的方法
/**
     * 动态sql where
     * @param student
     * @return
     */
    List<Student> selectStudentWhere(Student student);
  • mapper中的配置改为
<select id="selectStudentWhere" resultType="Student">
    select * from Student
    <where>
      <if test="name != null and name!=''">
        name = #{name}
      </if>
      <if test="age > 0">
        or age > #{age}
      </if>
    </where>
  </select>

在这里插入图片描述

  • 在测试类中加入测试
 @Test
    public  void testSelectStudentWhere(){
        Student student = new Student();
        student.setAge(18);
        List<Student> students = studentDao.selectStudentWhere(student);
        for (Student s :students) {
            System.out.println(s);
        }
    }

在这里插入图片描述

3、动态SQL之foreach标签

  • <foreach/>标签用于实现对于数组与集合的遍历。对其使用,需要注意:
    • collection表示要遍历的集合类型, list , array等。
    • open、close、 separator为对遍历内容的SQL拼接。
  • 在StudentDao中添加对应的方法
/**
     * 动态sql foreach
     * @param list
     * @return
     */
    List<Student> selectStudentList(List<Integer> list);
  • 在mapper中添加配置
<select id="selectStudentList" resultType="Student">
    select * from Student where id in
        <foreach collection="list" open="(" close=")" separator="," item="id">
          #{id}
        </foreach>
  </select>

在这里插入图片描述

  • 在测试类中添加测试方法
  @Test
    public  void selectStudentList(){
        List<Integer> list = new ArrayList<Integer>();
        list.add(1002);
        list.add(1003);
        List<Student> students = studentDao.selectStudentList(list);
        for (Student s :students) {
            System.out.println(s);
        }
    }

在这里插入图片描述

4、动态SQL之代码片段

  • <sql/>标签用于定义SQL片段,以便其它 SQL标签复用。而其它标签使用该SQL片段,需要使用<include/>子标签。该<sql/>标签可以定义SQL语句中的任何部分,所以<include/>子标签可以放在动态SQL的任何位置。
  • Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。
  • 在mapper文件中定义
<sql id="selectStudent">
    select * from Student
</sql>

在这里插入图片描述

  • 重新执行测试是没有问题的

五、配置文件

1、主配置文件

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

2、properties(属性)

  • 在properties中可以配置数据库的相关连接信息,相当于将dataSource中的信息提取出来方便管理
  • 有两种写法:一种是直接写在当前的配置文件里,另一种是新建一个专门存放数据库相关连接信息的配置文件,比较推荐的是第二种写法
  • 第一种:在mapper中配置
<properties>
      <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
      <property name="jdbc.url" value="jdbc:mysql:///student?characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
      <property name="jdbc.username" value="root"/>
      <property name="jdbc.password" value="root"/>
  </properties>

在这里插入图片描述

  • 第二种:通过配置文件引入
  • 创建一个jdbc.properties文件
    在这里插入图片描述
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///student?characterEncoding=UTF-8&serverTimezone=UTC
jdbc.username=root
jdbc.password=root
  • 在properties中进行配置
 <!--
  配置连接数据库的信息
    resource属性:用于指定properties配置文件的位置,要求配置文件必须在类路径下 resource="jdbcConfig.properties"
    url属性: URL: Uniform Resource Locator 统一资源定位符
                   http://localhost:8080/mystroe/CategoryServlet
                   协议      主机     端口
             URI:Uniform Resource Identifier 统一资源标识符
                  /mystroe/CategoryServlet
                  它是可以在web应用中唯一定位一个资源的路径
  -->
<properties resource="jdbc.properties"/>

在这里插入图片描述

3、typeAliases(类型别名)

<typeAliases> 
	 <!-- 单个别名定义 --> 
	 <typeAlias alias="user" type="com.bin.domain.User"/>
	 <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> 
	 <package name="com.bin.domain"/>
	 <package name="其它包"/>
</typeAliases>

4、mappers(映射器)

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

六、多表查询

  • 前提准备:创建两张表:一张User表,一张Account表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
	`id` INT (11),
	`username` VARCHAR (96),
	`birthday` DATETIME ,
	`sex` CHAR (3),
	`address` VARCHAR (768)
); 
INSERT INTO `user` (`id`, `username`, `birthday`, `sex`, `address`) VALUES('41','老王','2018-02-27 17:47:08','男','beijing');
INSERT INTO `user` (`id`, `username`, `birthday`, `sex`, `address`) VALUES('42','小二王','2018-03-02 15:09:37','女','北京');
INSERT INTO `user` (`id`, `username`, `birthday`, `sex`, `address`) VALUES('43','小二王','2018-03-04 11:34:34','女','北京');
INSERT INTO `user` (`id`, `username`, `birthday`, `sex`, `address`) VALUES('45','老李','2018-03-04 12:04:06','男','北京');
INSERT INTO `user` (`id`, `username`, `birthday`, `sex`, `address`) VALUES('46','老王','2018-03-07 17:37:26','男','北京');
INSERT INTO `user` (`id`, `username`, `birthday`, `sex`, `address`) VALUES('48','小马宝莉','2018-03-08 11:44:00','女','北京');

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,46,1000),(2,45,1000),(3,46,2000);

1、一对一查询(多对一)

  • 一个用户(User)可以有多个账户(Account)。具体关系如下:
    在这里插入图片描述
  • 需求:查询所有账户信息,关联查询下的用户信息。
  • 分析:
    • 一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。
    • 从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

实现一对一可以有两种方式
(新建一个maven工程,参照入门案例创建好对应的配置文件)

  • 主配置文件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="MySQL.properties"/>
  <settings>
    <!--设置mybatis输出日志-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
  </settings>
  <environments default="mysql">
    <environment id="mysql">
      <transactionManager type="JDBC"></transactionManager>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="dao/UserDao.xml"/>
    <mapper resource="dao/AccountDao.xml"/>
  </mappers>
</configuration>
  • 方式一(通过一个AccountUser类同时接收Account和User的共同信息)
  • 创建Account的实体类
package domain;
public class Account{
	private Integer id;
	private Integer uid;
	private Double money;
	public Integer getId() { 
		return id; 
	}
	public void setId(Integer id) { 
		this.id = id; 
	} 
	public Integer getUid() { 
		return uid; 
	} 
	public void setUid(Integer uid) { 
		this.uid = uid; 
	} 
	public Double getMoney() { 
		return money; 
	} 
	public void setMoney(Double money) { 
		this.money = money; 
	} 		
	@Override
	public String toString() { 
		return "Account [id=" + id + 
				", uid=" + uid + 
				", money=" + money + 
				"]"; 
	} 
}
  • 创建User的实体类
package domain;

import java.util.Date;
import java.util.List;

public class User{
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    
    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getAddress() {
        return address;
    }

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

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

  • Sql语句的编写
    实现查询账户信息时,也要查询账户所对应的用户信息。
SELECT account.*, user.username, user.address FROM account, user WHERE account.uid = user.id

在这里插入图片描述

  • 创建一个AccountUser类
  • 为了能够封装上面SQL语句的查询结果,定义 AccountUser类中要包含账户信息同时还要包含用户信息,所以我们要在定义AccountUser类时可以继承User类。
package 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 + '\'' +
                '}';
    }
}

  • 定义AccountDao接口
package dao;

import domain.Account;
import domain.AccountUser;

import java.util.List;

public interface AccountDao {
    /**
     * 查询全部操作
     * @return
     */
    List<AccountUser> findAllAccount();
}
  • 在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.bin.dao.AccountDao"> 
	<!-- 配置查询所有操作--> 
	<select id="findAll" resultType="domain.AccountUser"> 
		select a.*,u.username,u.address from account a,user u where a.uid =u.id; 
	</select> 
</mapper>
  • 编写AccountTest测试类
package AccountDaoTest;

import dao.AccountDao;
import domain.Account;
import domain.AccountUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    AccountDao accountDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        accountDao = sqlSession.getMapper(AccountDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void testFindAllAccount(){
        List<AccountUser> accountUsers = accountDao.findAllAccount();
        for (AccountUser a:accountUsers) {
            System.out.println(a);
        }
    }
   

在这里插入图片描述

  • 方式二
  • 使用resultMap,定义专门的resultMap用于映射一对一查询结果。 通过面向对象的(has a)关系可以得知,我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。
  • 此时不需要AccountUser类了
  • 修改Account类
package domain;

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

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

在这里插入图片描述

  • 修改AccountDao接口
  • 第二种方式,将返回值改为了Account类型。 因为Account类中包含了一个User类的对象,它可以封装账户所对应的用户信息。
package dao;

import domain.Account;
import domain.AccountUser;

import java.util.List;

public interface AccountDao {
    /**
     * 查询全部操作
     * @return
     */
    List<Account> findAllAccount();
}
  • 重新配置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="dao.AccountDao">
  <resultMap id="accountUserMap" type="domain.Account">
    <id property="id" column="aid"></id>
    <result property="uid" column="uid"></result>
    <result property="money" column="money"></result>
    <association property="user" column="uid" javaType="domain.User">
      <id property="id" column="id"></id>
      <result property="username" column="username"></result>
      <result property="birthday" column="birthday"></result>
      <result property="sex" column="sex"></result>
      <result property="address" column="address"></result>
    </association>
  </resultMap>
  <select id="findAll" resultMap="accountUserMap">
    select u.*,a.id as aid,a.money from account a,user u where a.uid = u.id
  </select>
</mapper>

在这里插入图片描述

  • 在测试类中添加测试方法
 @Test
    public void testFindAll(){
        List<Account> accounts = accountDao.findAll();
        for (Account account: accounts) {
            System.out.println(account);
            System.out.println(account.getUser());
        }
    }

在这里插入图片描述

2、一对多查询

  • 需求:查询所有用户信息及用户关联的账户信息。
  • 分析:用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。
  • Sql语句的编写
select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid

在这里插入图片描述

  • User类加入List<Account>
package domain;

import java.util.Date;
import java.util.List;

public class User{
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    //一对多
    private List<Account> account;


    public List<Account> getAccount() {
        return account;
    }

    public void setAccount(List<Account> account) {
        this.account = account;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getAddress() {
        return address;
    }

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

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

  • 在UserDao接口编写方法
package dao;

import domain.User;

import java.util.List;

public interface UserDao {
    /**
     * 查询所有操作
     * @return
     */
    List<User> findAll();
}
  • 在UserDao.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="dao.UserDao">

  <resultMap id="UserAccountMap" type="domain.User">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="birthday" property="birthday"></result>
    <result column="sex" property="sex"></result>
    <!--one2many-->
    <collection property="account" ofType="domain.Account">
      <id column="aid" property="id"></id>
      <result column="uid" property="uid"></result>
      <result column="money" property="money"></result>
    </collection>
  </resultMap>

  <select id="findAll" resultMap="UserAccountMap">
    select * from user u left outer join account a on u.id = a.uid
  </select>
</mapper>

在这里插入图片描述

  • 编写UserDaoTest方法
package UserDaoTest;

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

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void  testFindAll(){
        List<User> users = userDao.findAll();
        for (User user:users) {
            System.out.println(user);
            System.out.println(user.getAccount());
        }
    }
}

在这里插入图片描述

3、多对多查询

新建一个maven工程,配置好主配置文件

  • 前期准备:创建一个Role表,一张user_role中间表
DROP TABLE IF EXISTS `role`;

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);
  • 用户与角色的关系模型
    在这里插入图片描述
  • 需求:实现查询所有对象并且加载它所分配的用户信息。

实现Role 到User 多对多

  • 分析:查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE表)才能关联到用户信息。
  • Sql语句实现
SELECT u.*,r.id AS rid,r.role_name,r.role_desc FROM ROLE r  LEFT OUTER JOIN user_role ur  ON r.id = ur.rid  LEFT OUTER JOIN USER u ON u.id = ur.uid;

在这里插入图片描述

  • 编写实体类Role
package domain;

import java.util.List;

public class Role {
    private Integer id;
    private String roleName;
    private String roleDesc;

    private List<User> user;

    public List<User> getUser() {
        return user;
    }

    public void setUser(List<User> user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

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

    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{" +
                "id=" + id +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }
}

在这里插入图片描述

  • User实体类
package domain;

import java.util.Date;
import java.util.List;

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

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getAddress() {
        return address;
    }

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

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

  • 编写RoleDao接口
package dao;

import domain.Role;

import java.util.List;

public interface RoleDao {
    /**
     * 查询所有操作
     * @return
     */
    List<Role> findAll();
}
  • 编写RoleDao.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="dao.RoleDao">
  <resultMap id="RoleMap" type="domain.Role">
    <id property="id" column="rid"></id>
    <result column="ROLE_NAME" property="roleName"></result>
    <result column="ROLE_DESC" property="roleDesc"></result>
    <collection property="user" ofType="domain.User">
      <id property="id" column="id"></id>
      <result column="username" property="username"></result>
      <result column="address" property="address"></result>
      <result column="birthday" property="birthday"></result>
      <result column="sex" property="sex"></result>
    </collection>
  </resultMap>
  <select id="findAll" resultMap="RoleMap">
    select u.*,r.id as rid,r.role_name,r.role_desc from role r
    left outer join user_role ur  on r.id = ur.rid
    left outer join user u on u.id = ur.uid
  </select>
</mapper>

在这里插入图片描述

  • 编写RoleTest测试类
package RoleDaoTest;

import dao.RoleDao;
import dao.UserDao;
import domain.Role;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    RoleDao roleDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        roleDao = sqlSession.getMapper(RoleDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        List<Role> roles = roleDao.findAll();
        for (Role role: roles) {
            System.out.println(role);
            System.out.println(role.getUser());
        }
    }
}

在这里插入图片描述

实现User到Role的多对多

  • 需求:实现查询所有用户信息并关联查询出每个用户的角色列表。
  • Sql语句的实现
 select u.*,r.id as rid,r.role_name,r.role_desc from user u
    left outer join user_role ur  on u.id = ur.uid
    left outer join role r on r.id = ur.rid

在这里插入图片描述

  • 修改User实体类
package 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 Date birthday;
    private String sex;
    private String address;

    private List<Role> roles;

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

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

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getAddress() {
        return address;
    }

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

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

在这里插入图片描述

  • 编写UserDao接口
package dao;

import domain.User;

import java.util.List;

public interface UserDao {
    /**
     * 查询所有操作
     * @return
     */
    List<User> findAll();
}
  • 编写UseDao.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="dao.UserDao">

  <resultMap id="UserAccountMap" type="domain.User">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="birthday" property="birthday"></result>
    <result column="sex" property="sex"></result>
    <collection property="roles" ofType="domain.Role">
      <id property="id" column="rid"></id>
      <result property="roleName" column="ROLE_NAME"></result>
      <result property="roleDesc" column="ROLE_DESC"></result>
    </collection>
  </resultMap>

  <select id="findAll" resultMap="UserAccountMap">
    select u.*,r.id as rid,r.role_name,r.role_desc from user u
    left outer join user_role ur  on u.id = ur.uid
    left outer join role r on r.id = ur.rid
  </select>
</mapper>

在这里插入图片描述

  • 编写UerDaoTest测试类
package UserDaoTest;

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

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void  testFindAll(){
        List<User> users = userDao.findAll();
        for (User user:users) {
            System.out.println(user);
            System.out.println(user.getRoles());
        }
    }
}

在这里插入图片描述

七、延迟加载策略

1、什么是延迟加载

  • 延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
  • 坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

2、实现延迟加载

新建maven工程,将一对一、一对多的内容复制过来

  • 需求: 查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。
  • 通过association、collection实现一对一及一对多映射。association、collection具备延迟加载功能。

使用association实现延迟加载

  • 需求: 查询账户信息同时查询用户信息。
  • 修改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="dao.AccountDao">
  <resultMap id="accountUserMap" type="domain.Account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!--    一对一的关系映射,配置封装user的内容
        select属性指定的内容,查询用户的唯一标识,
        column属性指定的内容,用户根据id查询,所需要的参数的值
-->
    <association property="user" column="uid" javaType="domain.User" select="dao.UserDao.findByID"/>
  </resultMap>
  <select id="findAll" resultMap="accountUserMap">
    select * from account
  </select>
</mapper>
  • 在官方文档中可以查询settings关键字,可以看到有关延迟加载,当然还有很多其他的配置,包括下面要讲的缓存
    在这里插入图片描述
  • 在主配置文件中进行配置
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <properties resource="MySQL.properties"/>
  <settings>
  <!--    开启延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
      <!--设置mybatis输出日志-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
  </settings>
  <environments default="mysql">
    <environment id="mysql">
      <transactionManager type="JDBC"></transactionManager>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="dao/UserDao.xml"/>
    <mapper resource="dao/AccountDao.xml"/>
  </mappers>
</configuration>

在这里插入图片描述

  • 修改测试类
package AccountDaoTest;

import dao.AccountDao;
import domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    AccountDao accountDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        accountDao = sqlSession.getMapper(AccountDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        List<Account> accounts = accountDao.findAll();
//        for (Account account: accounts) {
//            System.out.println(account);
//            System.out.println(account.getUser());
//        }
    }
}

在这里插入图片描述
在这里插入图片描述
使用Collection实现延迟加载

  • UerDao.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="dao.UserDao">

  <resultMap id="UserAccountMap" type="domain.User">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="birthday" property="birthday"></result>
    <result column="sex" property="sex"></result>
    <!-- collection是用于建立一对多中集合属性的对应关系 
    ofType用于指定集合元素的数据类型 
    select是用于指定查询账户的唯一标识(账户的dao全限定类名加上方法名称) 
    column是用于指定使用哪个字段的值作为条件查询 
    -->
    <collection property="account" ofType="domain.Account" select="dao.AccountDao.findAccountByID" column="id"></collection>
  </resultMap>

  <select id="findAll" resultMap="UserAccountMap">
    select * from user
  </select>
</mapper>
  • UserDaoTest测试类
package UserDaoTest;

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

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void  testFindAll(){
        List<User> users = userDao.findAll();
//        for (User user:users) {
//            System.out.println(user);
//            System.out.println(user.getAccount());
//        }
    }
}

在这里插入图片描述

八、缓存

  • 像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
  • Mybatis中缓存分为一级缓存,二级缓存。
    在这里插入图片描述

1、一级缓存

  • 证明一级缓存的存在:一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。
  • 新建一个Maven工程,复制延迟加载的内存,将Account的相关内容删除,只留下User的内容
  • User实体类
package domain;

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

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

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

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

    public String getAddress() {
        return address;
    }

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

}

  • 编写UserDao接口
package dao;

import domain.User;


public interface UserDao {

    /**
     * 根据ID查询
     * @param id
     * @return user
     */
    User findByID(Integer id);

}
  • 编写UserDao.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="dao.UserDao">
  <cache/>

  <select id="findByID" parameterType="Integer" resultType="domain.User">
    select * from user where id = #{id}
  </select>
</mapper>
  • 编写测试方法
package UserDaoTest;

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

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    SqlSessionFactory sqlSessionFactory;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    /**
     * 测试一级缓存
     */
       @Test
    public void testFirstLevelCache(){
        User user = userDao.findByID(41);
        User user1 = userDao.findByID(41);
        System.out.println(user);
        System.out.println(user1);
        System.out.println(user==user1);
        
        //关闭sqlSession,重新打开会清除一级缓存
        //sqlSession.close();
        //sqlSession = sqlSessionFactory.openSession();

        //清除缓存
        sqlSession.clearCache();
        userDao = sqlSession.getMapper(UserDao.class);

        System.out.println(user);
        System.out.println(user1);
        //不清除缓存的话,第二次执行访问的是缓存的内容
        System.out.println(user==user1);
    }
}

在这里插入图片描述
在这里插入图片描述
一级缓存的分析

  • 一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
    在这里插入图片描述
  • 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
  • 得到用户信息,将用户信息存储到一级缓存中。
  • 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
  • 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

2、二级缓存

  • 二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
    在这里插入图片描述
  • 首先开启mybatis的二级缓存。 sqlSession1去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。
  • 如果SqlSession3去执行相同 mapper映射下sql,执行commit提交,将会清空该 mapper映射下的二级缓存区域的数据。
  • sqlSession2去查询与sqlSession1相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

二级缓存的开启与关闭

  • 第一步:在SqlMapConfig.xml文件开启二级缓存
  • 因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。
<settings>
	 <!-- 开启二级缓存的支持 --> 
 	<setting name="cacheEnabled" value="true"/> 
 </settings>
  • 第二步:配置相关的Mapper映射文件
  • <cache>标签表示当前这个mapper映射将使用二级缓存,区分的标准就看mapper的namespace值。
<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper
 	 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
 	 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
<mapper namespace="dao.UserDao"> 
 	 <!-- 开启二级缓存的支持 -->
 	  <cache></cache> 
</mapper>
  • 第三步:配置statement上面的useCache属性
  • 将UserDao.xml映射文件中的<select>标签中设置useCache=”true”代表当前这个statement要使用二级缓存,如果不使用二级缓存可以设置为false。
  • 注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存
<!-- 根据id查询 --> 
<select id="findById" resultType="domain.User" parameterType="int" useCache="true"> 
	select * from user where id = #{uid} 
</select>
  • 编写测试类测试
 @Test
    public void testSecondLevelCache(){
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserDao userDao1 =  sqlSession1.getMapper(UserDao.class);
        User user1 = userDao1.findByID(41);
        System.out.println(user1);
        sqlSession1.close();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserDao userDao2 =  sqlSession2.getMapper(UserDao.class);
        User user2 = userDao2.findByID(41);
        System.out.println(user2);
        sqlSession1.close();
        System.out.println(user1==user2);
    }

在这里插入图片描述

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

九、注解开发

1、使用注解实现CRUD

  • 使用注解开发:就可以不编写对应mapper配置文件
  • mybatis的常用注解说明
@Insert:实现新增 
@Update:实现更新 
@Delete:实现删除 
@Select:实现查询 
@Result:实现结果集封装
@Results:可以与@Result一起使用,封装多个结果集 
@ResultMap:实现引用@Results定义的封装 
@One:实现一对一结果集封装 
@Many:实现一对多结果集封装 
@SelectProvider: 实现动态SQL映射 
@CacheNamespace:实现注解二级缓存的使用

使用注解实现CRUD操作(即将入门案例改为注解的方式)

  • 主配置文件
<?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="MySQL.properties"/>
  <settings>
    <setting name="cacheEnabled" value="true"/>
  </settings>
  <typeAliases>
    <package name="com.bin.domain"/>
  </typeAliases>
  <environments default="mysql">
    <environment id="mysql">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
  <!--
   配置dao接口的位置,它有两种方式 
   第一种:使用mapper标签配置class属性 
   第二种:使用package标签,直接指定dao接口所在的包
   注意:使用注解开发,配置的位置mapper文件不能存在 
   -->
    <package name="com.bin.dao"/>
  </mappers>
</configuration>
  • 更改StudentDao接口
package com.bin.dao;

import com.bin.domain.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface StudentDao {
    /**
     * 查询所有
     * @return  List<Student>
     */
    @Select("select * from Student")
    List<Student> findAll();

    /**
     * 插入操作
     * @param student Student对象
     */
    @Insert("insert into Student(id,name,email,age) values (#{id},#{name},#{email},#{age})")
    void insert(Student student);

    /**
     * 更新操作
     * @param student
     */
    @Update("update Student set name = #{name},email = #{email},age = #{age} where id = #{id}")
    void update(Student student);

    /**
     * 删除操作
     * @param id
     */
    @Delete("delete from Student where id = #{id}")
    void delete(Integer id);
}
  • 重新执行测试结果没有问题

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

  • 实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助@Results注解,@Result注解,@One注解,@Many注解。
  • 复杂关系映射的注解说明
@Results注解 
代替的是标签<resultMap> 
该注解中可以使用单个@Result注解,也可以使用@Result集合 
@Results({@Result(),@Result()})或@Results(@Result())

@Resutl注解 
代替了 <id>标签和<result>标签 
@Result 中 属性介绍: 
	id 是否是主键字段 
	column 数据库的列名 
	property需要装配的属性名 
	one 需要使用的@One注解(@Result(one=@One)())) 
	many 需要使用的@Many注解(@Result(many=@many)())) 

@One注解(一对一) 
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One注解属性介绍: 
	select 指定用来多表查询的sqlmapper 
	fetchType会覆盖全局的配置参数lazyLoadingEnabled。
使用格式: 
	@Result(column=" ",property="",one=@One(select="")) 

@Many注解(多对一) 
代替了<collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。 
注意:聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList)但是注解中可以不定义; 
使用格式: 
	@Result(property="",column="",many=@Many(select=""))

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

  • 需求: 加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)
  • 更改User类和Account类
package com.bin.domain;

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

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

    public Integer getUserId() {
        return UserId;
    }

    public void setUserId(Integer userId) {
        UserId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

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

    public String getUserSex() {
        return userSex;
    }

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

    public String getUserAddress() {
        return userAddress;
    }

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

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

package com.bin.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 Integer getId() {
        return id;
    }

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

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

  • 修改AccountDao接口
import  com.bin.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

public interface AccountDao {
    /**
     * 查询全部操作
     * @return
     */
    @Select("select *from account")
    @Results(id ="accountMap",value = {
            @Result(id = true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(column = "uid",property = "user",one = @One(select = "com.bin.dao.UserDao.findByID",fetchType = FetchType.LAZY))
    })
    List<Account> findAll();
}

在这里插入图片描述

  • 修改UserDao接口
package com.bin.dao;

import com.bin.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;
public interface UserDao {

    /**
     * 查询所有操作
     * @return user
     */
    @Select("select * from user")
    @Results(id="UserMap",value = {
            @Result(id = true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "birthday",property = "userBirthday"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "address",property = "userAddress"),
    })
    List<User> findAll();
    /**
     * 根据ID查询一个
     * @return User
     */
    @Select("select * from user where id = #{uid}")
    @ResultMap("UserMap")
    User findByID(Integer id);
 }
  • 编写测试方法
import com.bin.dao.AccountDao;
import com.bin.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class test {
    InputStream inputStream;
    SqlSession sqlSession;
    AccountDao accountDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        accountDao = sqlSession.getMapper(AccountDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        List<Account> accounts = accountDao.findAll();
       /* for (Account account: accounts) {
            System.out.println(account);
            System.out.println(account.getUser());
        }*/
    }
}

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

2)、使用注解实现一对多复杂关系映射

  • 修改User类
package com.bin.domain;

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

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

    private List<Account> account;

    public List<Account> getAccount() {
        return account;
    }

    public void setAccount(List<Account> account) {
        this.account = account;
    }

    public Integer getUserId() {
        return UserId;
    }

    public void setUserId(Integer userId) {
        UserId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

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

    public String getUserSex() {
        return userSex;
    }

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

    public String getUserAddress() {
        return userAddress;
    }

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

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

  • 修改UserDao接口
package com.bin.dao;

import com.bin.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;
public interface UserDao {

    /**
     * 查询所有操作
     * @return user
     * @Many: 相当于<collection>的配置 
     * select属性:代表将要执行的sql语句 
     * fetchType属性:代表加载方式,一般如果要延迟加载都设置为LAZY的值
     */
    @Select("select * from user")
    @Results(id="UserMap",value = {
            @Result(id = true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "birthday",property = "userBirthday"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "address",property = "userAddress"),
            @Result(column = "id",property = "account",
                    many = @Many(select = "com.bin.dao.AccountDao.findAccountByID",
                            fetchType = FetchType.EAGER))
    })
    List<User> findAll();
}

  • 更改AccountDao接口
package com.bin.dao;

import  com.bin.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

public interface AccountDao {
    /**
     * 查询全部操作
     * @return
     */
    @Select("select *from account")
    @Results(id ="accountMap",value = {
            @Result(id = true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(column = "uid",property = "user",
                    one = @One(select = "com.bin.dao.UserDao.findByID",
                            fetchType = FetchType.LAZY))
    })
    List<Account> findAll();

    /**
     *
     * @param id
     * @return
     */
    @Select("select * from account where uid = #{id}")
    List<Account> findAccountByID(Integer id);

}
  • 编写测试类
package UserDaoTest;

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

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

public class test {
    InputStream inputStream;
    SqlSessionFactory sqlSessionFactory;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }
    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        inputStream.close();
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        List<User> users = userDao.findAll();
        for (User user: users) {
            System.out.println(user);
            System.out.println(user.getAccount());
        }
    }
}

加粗样式

3、mybatis基于注解的二级缓存

  • 在SqlMapConfig中开启二级缓存支持
<settings>
    <setting name="cacheEnabled" value="true"/>
 </settings>
  • 在持久层接口中使用注解配置二级缓存
package com.bin.dao;

import com.bin.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;
@CacheNamespace(blocking = true)
public interface UserDao {

    /**
     * 查询所有操作
     * @return user
     */
    @Select("select * from user")
    @Results(id="UserMap",value = {
            @Result(id = true,column = "id",property = "userId"),
            @Result(column = "username",property = "userName"),
            @Result(column = "birthday",property = "userBirthday"),
            @Result(column = "sex",property = "userSex"),
            @Result(column = "address",property = "userAddress"),
            @Result(column = "id",property = "account",
                    many = @Many(select = "com.bin.dao.AccountDao.findAccountByID",
                            fetchType = FetchType.EAGER))
    })
    List<User> findAll();
    /**
     * 根据ID查询一个
     * @return User
     */
    @Select("select * from user where id = #{uid}")
    @ResultMap("UserMap")
    User findByID(Integer id);
}

在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-17 11:46:11  更:2021-07-17 11:49:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 15:09:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码