MyBatis

MyBatis

MyBatis介绍

MyBatis_第1张图片

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

IBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层(Dao 数据访问层)框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

下载地址: MyBatis下载地址:https://github.com/mybatis/mybatis-3/releases

使用版本:3.4.5

Hibernate 冬眠   全自动框架  SQL语句可以自动生成,不用人工书写SQL! 灵活度,性能差,笨重。SQL定制麻烦!
自动挡汽车汽车!
-----------------------------------------------------------------------------------------------------
    
MyBatis          半自动     SQL语句还是需要自己书写,后期有一些插件可以自动生成SQL! 灵活  定制SQL! 
手自一体汽车!

MyBatis Plus 插件!

ORM概念: Object Ralation Mapping 对象关系映射 框架

数据库表 和 程序中的实体类 有对应关系(映射关系)的框架,叫做ORM框架(对象关系映射)

      数据库表  tb_user ------>实体类  User
      
​     数据库表中字段 username----->实体类的属性 username

​     数据库表中字段的类型 varchar(20) ----->实体类中属性的类型  String 


数据库表 和 程序实体类  有对应关系的持久层框架 就叫ORM框架!

常见的ORM框架哪些:

MyBatis 典型的ORM框架,数据库表和程序实体类存在映射关系的框架!  SSM  SpringMvc+Spring+MyBatis

Hibernate          SSH  Struts2+Spring+Hibernate 
Spring Data  JPA
......

MyBatis特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。

  • 插件丰富,开源框架,有很多第三方插件,辅助开发,甚至MyBatis自定义插件!

    MyBatis Plus插件!

1.MyBatis环境搭建

1.1.环境要求

JDK环境:jdk1.8

IDE环境:IDEA

数据库环境:MySQL 5.5.X

MyBatis:3.4.5

maven:3.6.X

1.2.准备数据库

1.2.1.创建MyBatis测试的数据库

新建数据库:mybatis

字符集选择:utf8 – UTF-8 Unicode

排序规则选择:utf8_general_ci

1.2.2.导入需要测试的SQL语句

DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `sex` char(1) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', '王五', '2018-09-06', '1', '四川成都');
INSERT INTO `tb_user` VALUES ('10', '张三', '2014-07-10', '1', '北京市');
INSERT INTO `tb_user` VALUES ('16', '张小明', '2018-09-06', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('22', '陈小明', '2018-09-05', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('24', '张三丰', '2018-09-13', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('25', '陈小明', '2018-09-12', '1', '河南郑州');
INSERT INTO `tb_user` VALUES ('26', '王五', '2018-09-05', '0', '河南郑州');

1.3.IDEA创建聚合项目

先创建一个普通的maven工程,把这个工程作为父工程

在父工程上点击右键选择新建—>module 创建子工程

父工程中的src没有卵用,直接删除!代码写在子工程中

注意:公司里边一般都是聚合项目

1.4.在父工程导入开发依赖

在父工程的pom.xml中导入依赖

<dependencies>

        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.4.5version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            
            <version>8.0.15version>
        dependency>

        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.16.18version>
            <scope>providedscope>
        dependency>

dependencies>

1.5.创建一个实体层

src/main/java ->com.dream.pojo

用lombok的注解

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

  private Integer id;
  private String username;
  private String birthday;//Mysql中Date程序中可以String表示,好处就是不用类型转换
  private String sex;
  private String address;

}

1.6.创建一个MyBatis核心配置文件:mybatis-config.xml

src/main/resources -> mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    

    
    <environments default="java2105">
        
        <environment id="java2105">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>

    
    <mappers>
        
     	<mapper resource="mappers/UserMapper.xml"/>  
    mappers>
configuration>

1.7.创建Mapper层接口

Mapper层相当于MVC模式下的Dao层

src/main/java -> com.dream.mapper -> UserMapper

package com.dream.mapper;

import com.dream.pojo.User;

import java.util.List;

public interface UserMapper {
    List<User> findUsers();
}

1.8.给接口创建一个mapper.xml文件

src/main/resources -> com.dream/mappers -> mapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.mapper.UserMapper">
    
    <select id="findUsers" resultType="com.dream.pojo.User">
        select * from tb_user
    select>
mapper>

1.9.给Mapper接口生成一个实现类

src/main/java -> com.dream.mapper -> UserMapperImpl

package com.dream.mapper;

import com.dream.pojo.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.IOException;
import java.io.InputStream;
import java.util.List;

public class UserMapperImpl implements UserMapper {

    public List<User> findUsers() {
        SqlSession sqlSession = null;
        try {
            //加载并读取配置文件
            String resources = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resources);
            //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //通过sqlSessionFactory工厂获取SqlSession对象!
            //SqlSession对象不是以前HttpSession,SqlSession类似于数据库JDBC里面的Connection
            sqlSession = sqlSessionFactory.openSession();
            //执行查询集合方法:  namespace+id
            List<User> users = sqlSession.selectList("com.dream.mapper.UserMapper.findUsers");
            return users;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            sqlSession.close();
        }
        return null;
    }

}

1.10.创建测试类

src/test/java -> com.dream.test -> TestMyBatis

package com.dream.test;

import com.dream.pojo.User;
import com.dream.mapper.UserMapper;
import com.dream.mapper.UserMapperImpl;
import org.junit.Test;

import java.util.List;

public class TestMybatis {

    @Test
    public void testFindusers(){
        UserMapper um=new UserMapperImpl();
        List<User> users = um.findUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }

}

1.11.把数据库信息单独封装到jdbc.properties中

src/main/resources -> jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=root

src/main/resources -> mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    

    
    <properties resource="jdbc.properties"/>

    
    <environments default="java2105">
        
        <environment id="java2105">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            dataSource>
        environment>
    environments>

    
    
    <mappers>
        
  		<mapper resource="mappers/UserMapper.xml"/>
    mappers>
configuration>

1.12.MyBatis配置优化-实体类别名

1.12.1.作用

Mapper.xml文件中实体类名字默认必须使用全名称,类似com.dream.pojo.User,为了书写简洁一些,可以给实体类取别名

1.12.2.给实体类取别名

方式1:给每个实体类单独取别名

不建议使用,实体类多了代码就要写很多

src/main/resources -> mybatis-config.xml

	
    <typeAliases>
        <typeAlias type="com.dream.pojo.User" alias="User"/>
    typeAliases>

方式2:给所有实体类包下所有实体类统一的取别名

建议使用

src/main/resources -> mybatis-config.xml

	
    <typeAliases>
        
        
        <package name="com.dream.pojo"/>
    typeAliases>

src/main/resources -> com.dream/mappers -> mapper.xml

	<select id="findUsers" resultType="User">
          select * from tb_user
    select>

1.13.mybatis-config.xml指定Mappers.xml文件的方式优化

1.13.1.原始方式-单独配置

src/main/resources -> mybatis-config.xml

	<mappers>
        
        
        <mapper resource="mappers/UserMapper.xml"/>
    mappers>

1.13.2.接口层所在的包的全路径

src/main/resources -> mybatis-config.xml


    
    
    

前提条件:1.接口的名字必须和mapper.xml文件名字相同

​ 2.接口的文件和mapper.xml必须在同一个路径下

文件满足以上两个后,仍然无法配置成功。原因是:

​ 如果xml文件在src下,但是不在resources下面默认是不会编译的

所以解决方案有如下两个:

方式1:每个mapper.xml单独配置

不建议使用

src/main/resources -> mybatis-config.xml

	<mappers>
        
        <mapper resource="mappers/UserMapper.xml"/>
    mappers>

在maven项目中配置xml规则

建议使用

MyBatis -> pom.xml

<build>
        <resources>
            <resource>
                
                <directory>src/main/javadirectory>
                <includes>
                    
                    <include>**/*.xmlinclude>
                    
                    <include>**/*.propertiesinclude>
                includes>
            resource>
            <resource>
                
                <directory>src/main/resourcesdirectory>
                <includes>
                    
                    <include>**/*.xmlinclude>
                    
                    <include>**/*.propertiesinclude>
                includes>
            resource>
        resources>
    build>

1.14.MyBatis使用注意事项

1.Mapper的namespace必须和mapper接口的全路径一致

2.Mapper接口的方法名必须和sql定义的id一致

3.Mapper接口中方法的输出参数类型必须和sql定义的resultType一致

2.MyBatis基础的增删改查

2.1.根据ID查询

2.1.1.先在UserMapper接口中添加一个方法

User findById(Integer id);

2.1.2.在UserMapper.xml文件中定义查询SQL

在MyBatis中,占位符:#{}${},占位符在JDBC底层最终还是翻译为?


    <select id="findById" resultType="com.dream.pojo.User" parameterType="integer">
        select * from tb_user where id=#{id}
    select>

2.1.3.UserMapper实现类UserMapperImpl

public User findById(Integer id) {
        SqlSession sqlSession=null;
        try {
            String resources="mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resources);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            sqlSession = sqlSessionFactory.openSession();
            User u= sqlSession.selectOne("com.dream.mapper.UserMapper.findById",id);
            return u;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
        return null;
    }

2.1.4.编写测试类TestMyBatis

	@Test
    public void testFindById(){
        User user = um.findById(10);
        System.out.println(user);
    }

2.2.封装MyBatis工具类:MyBatisUtils

src/main/java -> com.dream.utils ->MyBatisUtils

package com.dream.utils;

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.IOException;
import java.io.InputStream;

public class MyBatisUtils {

    /**
     *  01-获取SqlSession
     * @return
     */
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = null;
        try {
            //加载并读取配置文件
            String resources = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resources);
            //每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //通过sqlSessionFactory工厂获取SqlSession对象
            //SqlSession对象不是以前HttpSession,SqlSession类似于数据库JDBC里面的Connection
            //true:自动提交事务
            sqlSession = sqlSessionFactory.openSession(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }

    /**
     * 02-关闭SqlSession
     * @param sqlSession
     */
    public static void closeSqlSession(SqlSession sqlSession){
        if (sqlSession != null){
            sqlSession.close();
        }
    }
}

2.3.新增对象

2.3.1.先在UserMapper接口中添加一个方法

int addUser(User u);

2.3.2.在UserMapper.xml文件中定义查询SQL

insert 新增标签,MyBatis新增、删除、更新默认都返回int,也就是数据库受影响行数,不用写返回值

MyBatis参数类型可以省略! parameterType=“com.bruce.bean.User”

#{username}:中括号中的属性要与实体类的属性值相同

	
    <insert id="addUser">
        insert into tb_user values (null,#{username},#{birthday},#{sex},#{address})
    insert>

2.3.3.UserMapper实现类UserMapperImpl

MyBatis需要提交事务 的操作:增加、删除、更新

手动提交事务:sqlSession.commit();

手动回滚事务:sqlSession.rollback();

	/**
     * 增加、删除、更新:MyBatis需要提交事务
     * @param u
     * @return
     */
    public int addUser(User u) {
        SqlSession sqlSession = null;

        try {
            sqlSession = MyBatisUtils.getSqlSession();
            //执行新增
            int insert = sqlSession.insert("com.dream.mapper.UserMapper.addUser", u);
            //手动提交事务
            sqlSession.commit();
            return insert;
        } catch (Exception e){
            //手动回滚事务
            sqlSession.rollback();
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSqlSession(sqlSession);
        }
        return 0;
    }

2.3.4.编写测试类TestMyBatis

	@Test
    public void testInsert() {
        User u = new User(null, "隔壁老王", "1990-10-10", "男", "隔壁的");
        int count = um.addUser(u);
        System.out.println(count>0?"新增成功":"新增失败");
    }

2.4.MyBatis开启SQL日志

2.4.1.作用

方便开发人员调试程序,理解程序。

2.4.2.配置

	<settings>
        
        <setting name="logImpl" value="STDOUT_LOGGING" />
    settings>

src/main/resources -> mybatis-config.xml

注意:插入必须在之后添加,必须在之后添加。


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    

    
    <properties resource="jdbc.properties"/>
    
    <settings>
        
        <setting name="logImpl" value="STDOUT_LOGGING" />
    settings>
    
    
    <typeAliases>
        
        
        <package name="com.dream.pojo"/>
    typeAliases>

    
    <environments default="java2105">
        
        <environment id="java2105">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            dataSource>
        environment>
    environments>

    
    <mappers>
        
        
        
        
        <package name="com.dream.mapper"/>
    mappers>
configuration>

2.4.3.控制台SQL日志输出

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 1373810119.
==>  Preparing: insert into tb_user values (null,?,?,?,?) 
==> Parameters: 隔壁老王(String), 1990-10-10(String),(String), 隔壁的(String)
<==    Updates: 1
新增成功

2.5.MyBatis省略Mapper实现类

2.5.1.为什么省略?

1.实现类的代码大部分都是相似的冗余代码;

2.每次在接口中填加一个接口,实现必须重写,太鸡肋!

3.实现类会增加代码量,sql语句地址写在方法中是一串字符串,容易写错!

2.5.2.省略实现类出现的问题?

1.Mapper接口不能直接使用,因为接口不能创建对象!

2.UserMapper um = new UserMapperImpl();

2.5.3.Mybatis解决方式

底层使用JDK动态代理技术给Mapper接口生成了实现类对象

	SqlSession sqlSession = MyBatisUtils.getSqlSession();
    //sqlSession.getMapper(UserMapper.class):在框架底层,MyBatis使用JDK动态代理设置模式,给UserMapper接口生成了一个实现类!
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

2.6.MyBatis增删改提交事务

2.6.1.手动提交

src/test/java -> com.dream.test -> TestMyBatis

	@Test
    public void testInsert() {
        User u = new User(null, "老吴", "1990-10-10", "男", "隔壁的");
        int count = um.addUser(u);
        // 手动提交事务
        sqlSession.commit();
        System.out.println(count > 0 ? "新增成功" : "新增失败");
    }

2.6.2.自动提交

src/main/java -> com.dream.utils -> MyBatisUtils

//默认是false,这里加个true,true:自动提交事务
sqlSession = sqlSessionFactory.openSession(true);
package com.dream.utils;

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.IOException;
import java.io.InputStream;

public class MyBatisUtils {

    /**
     *  01-获取SqlSession
     * @return
     */
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = null;
        try {
            //加载并读取配置文件
            String resources = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resources);
            //每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //通过sqlSessionFactory工厂获取SqlSession对象
            //SqlSession对象不是以前HttpSession,SqlSession类似于数据库JDBC里面的Connection
            //true:自动提交事务
            sqlSession = sqlSessionFactory.openSession(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }

    /**
     * 02-关闭SqlSession
     * @param sqlSession
     */
    public static void closeSqlSession(SqlSession sqlSession){
        if (sqlSession != null){
            sqlSession.close();
        }
    }
}

2.7.根据ID删除

2.7.1.先在UserMapper接口中添加一个方法

int deleteUser(Integer id);

2.7.2.在UserMapper.xml文件中定义查询SQL

	
    <delete id="deleteUser">
        delete from tb_user where id=#{id}
    delete>

2.7.3.编写测试类TestMyBatis

  	@Test
    public void testDelete(){
        int count = um.deleteUser(10);
        System.out.println(count > 0 ? "删除成功" : "删除失败");
    }

2.8.根据ID更新

2.8.1.先在UserMapper接口中添加一个方法

int updateById(User u);

2.8.2.在UserMapper.xml文件中定义查询SQL

	<!--
    update 更新标签,MyBatis新增、删除、更新默认都返回int,也就是数据库受影响行数,不用写返回值!
    -->
    <update id="updateById">
        update tb_user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
    </update>

2.8.3.编写测试类TestMyBatis

	@Test
    public void testUpdateById(){
        //先把这个对象查询出来
        User u = userMapper.findById(16);
        u.setUsername("小舞");
        u.setSex("女");
        u.setAddress("斗罗大陆");

        //更新
        int count = userMapper.updateById(u);
        System.out.println(count>0?"更新成功":"更新失败");
    }

3.MyBatis条件查询操作

3.1.查询参数是对象场景

3.1.1.先在UserMapper接口中添加一个方法

//根据姓名和性别查询
List<User> findUserByUserNameAndSex(User u);

3.1.2.在UserMapper.xml文件中定义查询SQL

<select id="findUserByUserNameAndSex" resultType="User">
	select * from tb_user where username=#{username} and sex=#{sex}
select>

3.1.3.编写测试类TestMyBatis

@Test
public void testFindUserByUserNameAndSex(){
    User u=new User();
    u.setUsername("小舞");
    u.setSex("女");
    List<User> users = userMapper.findUserByUserNameAndSex(u);
    for(User user:users){
        System.out.println(user);
    }
}

3.2.查询参数是多参数场景

3.2.1.按照顺序传入参数

3.2.1.1先在UserMapper接口中添加一个方法
//根据出生日期区间查询  1991-01-01  -----  1993-12-12
List<User> findUsersByBirthday(String startBirthday,String endBirthday);
3.2.1.2在UserMapper.xml文件中定义查询SQL

这里用param1param2


3.2.1.3编写测试类TestMyBatis
@Test
public void testFindUsersByBirthday(){
      List<User> users = userMapper.findUsersByBirthday("2018-09-05", "2018-09-12");
      for (User user:users) {
         System.out.println(user);
      }
}

3.2.2.按照名字传入参数

3.2.2.1先在UserMapper接口中添加一个方法

这里用**@Param(“a”)@Param(“b”)**

这样就不用在意顺序

//根据出生日期区间查询  1991-01-01  -----  1993-12-12
List<User> findUsersByBirthdays(
    @Param("a") String startBirthday,
    @Param("b") String endBirthday);
3.2.2.2在UserMapper.xml文件中定义查询SQL
<select id="findUsersByBirthday" resultType="User">
	select * from tb_user where birthday between #{a} and #{b}
select>
3.2.2.3编写测试类TestMyBatis
@Test
public void testFindUsersByBirthdays(){
      List<User> users = userMapper.findUsersByBirthdays("2018-09-05", "2018-09-12");
      for (User user:users) {
         System.out.println(user);
      }
}

3.3.查询参数是Map的场景

使用场景:如果查询的参数多,那么建议使用Map带值

3.3.1.先在UserMapper接口中添加一个方法

//参数多的情况下
List<User> findUserByBirthdayAndSex(Map<String,Object> map);

3.3.2.在UserMapper.xml文件中定义查询SQL

<select id="findUserByBirthdayAndSex" resultType="User">
	select * from tb_user where birthday between #{a} and #{b} and sex=#{sex}
select>

3.3.3.编写测试类TestMyBatis

@Test
public void testFindUserByBirthdayAndSex(){
    Map<String,Object> map = new HashMap<String,Object>();
    map.put("a","1991-01-01");
    map.put("b","1995-01-01");
    map.put("sex","女");
    List<User> users = userMapper.findUserByBirthdayAndSex(map);
    for(User user:users){
        System.out.println(user);
    }
}

3.4.查询数目

3.4.1.先在UserMapper接口中添加一个方法

//查询男生数量
int getCountBySex(String sex);

3.4.2.在UserMapper.xml文件中定义查询SQL

<select id="getCountBySex" resultType="int">
	select count(id) from tb_user where sex=#{sex}
select>

3.4.3.编写测试类TestMyBatis

@Test
public void testGetCountBySex(){
    int count = userMapper.getCountBySex("男");
    System.out.println("男生的数量是:" + count);
}

3.5.查询返回值是Map场景

3.5.1.先在UserMapper接口中添加一个方法

//查询男生的 最大生日 和 最小的生日
Map<String,Object> getMaxAndMinBirthday(String sex);

3.5.2.在UserMapper.xml文件中定义查询SQL

<select id="getMaxAndMinBirthday" resultType="map">
	select max(birthday) 'max',min(birthday) 'min' from tb_user where sex=#{sex}
select>

3.5.3.编写测试类TestMyBatis

@Test
public void testGetMaxAndMinBirthday(){
    Map<String,Object> map = userMapper.getMaxAndMinBirthday("男");
    System.out.println("最大的生日是:"+map.get("max"));
    System.out.println("最小的生日是:"+map.get("min"));
}

3.6.查询返回值是List场景

3.6.1.先在UserMapper接口中添加一个方法

//查询男生 和 女生 的 最大生日 和 最小的生日
//select max(birthday),min(birthday),sex from tb_user GROUP BY sex;
List<Map<String,Object>> getInformation();

3.6.2.在UserMapper.xml文件中定义查询SQL

<select id="getInformation" resultType="map">
	select max(birthday) 'max',min(birthday) 'min',sex from tb_user group by sex
select>

3.6.3.编写测试类TestMyBatis

@Test
public void testGetInformation(){
    List<Map<String,Object>> mapList = userMapper.getInformation();
    for(Map<String,Object> map:mapList){
        System.out.println("最大生日:"+map.get("max"));
        System.out.println("最小生日:"+map.get("min"));
        System.out.println("性别:"+map.get("sex"));
        System.out.println("-------------------------");
    }
}

3.7.分页查询

3.7.1.先在UserMapper接口中添加一个方法

//分页查询
int getCount(Map<String,Object> map);

List<User> findPage(Map<String,Object> map);

3.7.2.在UserMapper.xml文件中定义查询SQL

<select id="getCount" resultType="int">
	select count(id) from tb_user where sex=#{sex}
select>

<select id="findPage" resultType="User">
	select * from tb_user where sex=#{sex} limit #{startIndex},#{pageIndex}
select>

3.7.3.编写测试类TestMyBatis

/**
*  测试分页查询
*/
@Test
public void testPage(){
	int pageIndex=2; //当前页码
    int pageSize=5;  //页面大小
    String sex="男"; //查询条件

    Map<String,Object> map=new HashMap<String, Object>();
    map.put("startIndex",(pageIndex-1)*pageSize);
    map.put("pageSize",pageSize);
    map.put("sex",sex);

    int count=um.getCount(map);
    int pageCount=count%pageSize==0?count/pageSize:count/pageSize+1;
    List<User> users = um.findPage(map);

    System.out.println("当前页码:"+pageIndex);
    System.out.println("页面大小:"+pageSize);
    System.out.println("总页数:"+pageCount);
    System.out.println("总条数:"+count);
    System.out.println("-------------------------------------------");
    for (User user : users) {
       System.out.println(user);
    }
}

3.8.模糊查询

3.8.1.先在UserMapper接口中添加一个方法

方式1:

//查询名字中包含"小"的学生
List<User> findUsersByName(String username);

方式2:

@Param(“a”)

//查询名字中包含"小"的学生
List<User> findUsersByNames(@Param("a") String username);

3.8.2.在UserMapper.xml文件中定义查询SQL

方式1:

<select id="findUsersByName" resultType="User">
	select * from tb_user where username like #{username}
select>

方式2:

<select id="findUsersByNames" resultType="User">
	select * from tb_user where username like '%${a}%'
select>

3.8.3.编写测试类TestMyBatis

方式1:

传参时加入%%

@Test
public void testLike1(){
    List<User> users = um.findUsersByName("%小%");
    for (User user : users) {
        System.out.println(user);
    }
}

方式2:

@Test
public void testLike2(){
   List<User> users = um.findUsersByName1("小");
   for (User user : users) {
      System.out.println(user);
   }
}

3.9.面试题1-MyBatis新增如何返回数据库新增成功之后的主键值ID?

方式1:性能高(因为只执行了1条SQL语句),只适用于数据库ID自增数据库,一般用于MySQL数据库

insert 新增标签,MyBatis新增、删除、更新默认都返回int,也就是数据库受影响行数,不用写返回值!

parameterType=“com.bruce.bean.User”:MyBatis参数类型可以省略

useGeneratedKeys=“true”:开启自增长映射

keyProperty=“id” :指定id所对应对象中的属性名

<insert id="addUser" parameterType="User" keyProperty="id" useGeneratedKeys="true">
	INSERT INTO tb_user VALUES (null,#{username},#{birthday},#{sex},#{address})
insert>

方式2:性能差(因为执行了2条SQL语句,先执行新增,再执行查询),适用于广泛数据库,一般用于Orange数据库

keyProperty=“id” :指定id所对应对象中的属性名

resultType=“int”:返回值类型

order=“AFTER”:在新增之后执行查询id语句,如果写BEFORE,则查询结果就是0

**SELECT LAST_INSERT_ID()**
<insert id="addUser" parameteType="User">
    <selectKey keyProperty="id" resultType="int" order="AFTER">
    	SELECT LAST_INSERT_ID()
    selectKey>
	INSERT INTO tb_user VALUES (null,#{username},#{birthday},#{sex},#{address})
insert>

3.10.面试题2-MyBatis中#和$区别

#{ }:实现的是sql语句的预处理参数,之后执行sql中用**?号代替,使用时不需用关注数据类型**,MyBatis自动实现数据类型的转换更加安全可以防止SQL注入

${ }:实现sql语句的直接拼接不做数据类型转换,需要自行判断数据类型,不能防止SQL注入

补充:1.Statement和PrepareStatement 的区别

​ 答:

​ 2.什么是SQL注入,如何防止SQL注入?

​ 答:

4.MyBatis动态SQL

应用场景:根据用户的查询的条件,SQL会动态的变化,也就是说SQL语句不是固定,灵活度更高,运行效率高!

准备测试的数据库tb_employee

DROP TABLE IF EXISTS `tb_employee`;
CREATE TABLE `tb_employee` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `email` varchar(50) COLLATE utf8_bin DEFAULT NULL,
  `gender` char(1) COLLATE utf8_bin DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

-- ----------------------------
-- Records of tb_employee
-- ----------------------------
INSERT INTO `tb_employee` VALUES ('7', 'Jerry', '[email protected]', '1', '33');
INSERT INTO `tb_employee` VALUES ('8', '刘备', '[email protected]', '1', '18');
INSERT INTO `tb_employee` VALUES ('9', '诸葛亮', '[email protected]', '1', '18');
INSERT INTO `tb_employee` VALUES ('10', '曹操', '[email protected]', '1', '18');
INSERT INTO `tb_employee` VALUES ('11', '小琼1', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('12', '小琼2', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('13', '小琼3', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('14', '小琼4', '[email protected]', '0', '18');
INSERT INTO `tb_employee` VALUES ('15', '小琼5', '[email protected]', '0', '18');

4.1.IF标签

重点掌握

EmployeeMapper.java接口

List<Employee> findEmpByNameAndGender(Employee employee);

EmployeeMapper.xml定义SQL语句

这里的1=1也可以写成2=2,目的是让代码不报错

<select id="findEmpByNameAndGender" resultType="Employee">
	select * from tb_employee where 1=1
    <if test="name!=null and gender!=''">
    	and name=#{name}
    if>
    <if test="name!=null and gender!=''">
    	and gender=#{gender}
    if>
select>

TestMyBatis.java测试类

/**
* select * from tb_employee where 1=1
* select * from tb_employee where 1=1 and name=?
* select * from tb_employee where 1=1 and gender=?
* select * from tb_employee where 1=1 and name=? and gender=?
*/
@Test
public void testFindEmpByNameAndGender(){
	Employee employee = new Employee();
	employee.setName("Jerry");
	employee.setGender("1");
	List<Employee> employees = em.findEmpByNameAndGender(employee);
	for (Employee e:employees) {
		System.out.println(e);
	}
}

4.2.IF标签+WHERE标签

重点掌握

上面的1=1可读性不高,因此用IF+WHERE标签配合使用,可读性更高。

EmployeeMapper.java接口

List<Employee> findEmpByNameAndGender(Employee employee);

EmployeeMapper.xml定义SQL语句

注:在if中会默认删除第一个and

<select id="findEmpByNameAndGender" resultType="Employee">
	select * from tb_employee
    <where>
    	<if test="name!=null and name!=''">
        	name=#{name}
        if>
        <if test="gender!=null and gender!=''">
        	and gender=#{gender}
        if>
    where>
select>

TestMyBatis.java测试类

/**
* select * from tb_employee where 1=1
* select * from tb_employee where 1=1 and name=?
* select * from tb_employee where 1=1 and gender=?
* select * from tb_employee where 1=1 and name=? and gender=?
*/
@Test
public void testFindEmpByNameAndGender(){
	Employee employee = new Employee();
	employee.setName("Jerry");
	employee.setGender("1");
	List<Employee> employees = em.findEmpByNameAndGender(employee);
	for (Employee e:employees) {
		System.out.println(e);
	}
}

4.3.IF标签+SET标签

重点掌握

应用场景:根据用户输入条件动态的改变SQL更新的字段

EmployeeMapper.java接口

int updateEmp(Employee employee);

EmployeeMapper.xml定义SQL语句

注:如果有多余的逗号(,)会自动剔除,建议都加上逗号,不加逗号会报错

<update id="updateEmp">
    update tb_employee
	<set>
    	<if test="name!=null and name!=''">
    		name=#{name},
		if>
		<if test="email!=null and email!=''">
			email=#{email},
        if>
        <if test="gender!=null and gender!=''">
            gender=#{gender},
        if>
        <if test="age!=null">
           	age=#{age},
        if>
    set>
update>

TestMyBatis.java测试类

@Test
public void testUpdate(){
	Employee employee=new Employee();

	employee.setId(5);
	employee.setName("赵四");
	//employee.setGender("1");

	int count = em.updateEmp(employee);
	System.out.println(count>0?"更新成功":"更新失败");

}

4.4.choose(when,otherwise)标签

EmployeeMapper.java接口

List<Employee> selectUserByChoose(Employee employee);

EmployeeMapper.xml定义SQL语句

when相当于case,otherwise相当于条件

注:相当于Java中的switch语法条件只能满足一个

<select id="selectUserByChoose" resultType="Employee">
	select * from tb_employee
    <where>
    	<choose>
        	<when test="name!=null and name!=''">
            	and name=#{name}
            when>
            <when test="gender!=null and gender!=''">
            	and gender=#{gender}
            when>
            <otherwise>
            	and age>18
            otherwise>
        choose>
    where>
select>

TestMyBatis.java测试类

@Test
public void testselectUserByChoose(){
	Employee employee=new Employee();
	employee.setName("小A");
	//employee.setGender("1");
	List<Employee> employees =em.selectUserByChoose(employee);
	for (Employee emp : employees) {
	System.out.println(emp);
	}
}

4.5.TRIM标签

用法1:

EmployeeMapper.java接口

List<Employee> selectUserByTrim(Employee employee);

EmployeeMapper.xml定义SQL语句

prefix前缀

prefixoverride:去掉第一个and或者是or


<select id="selectUserByTrim" resultType="Employee">
	select * from tb_employee
	<trim prefix="WHERE" prefixOverrides="AND|OR">
		<if test="name!=null and name!=''">
			and name=#{name}
		if>
		<if test="gender!=null and gender!=''">
			and gender=#{gender}
		if>
	trim>
select>

TestMyBatis.java测试类

@Test
public void selectUserByTrim(){
	Employee employee=new Employee();
    employee.setName("小A");
    //employee.setGender("1");
    List<Employee> employees = em.selectUserByTrim(employee);
    for (Employee emp : employees) {
       System.out.println(emp);
    }
}

用法2:

EmployeeMapper.java接口

int updateEmp1(Employee employee);

EmployeeMapper.xml定义SQL语句

prefix前缀

suffixOverrides(",")去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)


<update id="updateEmp1">
	update tb_employee
	<trim prefix="SET" suffixOverrides=",">
		<if test="name!=null and name!=''">
			name=#{name},
		if>
		<if test="email!=null and email!=''">
			email=#{email},
		if>
		<if test="gender!=null and gender!=''">
			gender=#{gender},
		if>
		<if test="age!=null">
			age=#{age},
		if>
	trim>
	where id=#{id}
update>

TestMyBatis.java测试类

@Test
public void testUpdate1(){
	Employee employee=new Employee();

	employee.setId(5);
	employee.setName("赵四");
	employee.setGender("1");

	int count = em.updateEmp1(employee);
	System.out.println(count>0?"更新成功":"更新失败");

}

4.6.FOREACH标签

重点

4.6.1.批量查询

EmployeeMapper.java接口

方式1:

//批量查询,查询ID是2或者4或者6的员工信息
List<Employee> selectEmps(int[] ids);

方式2:

//批量查询,查询ID是2或者4或者6的员工信息
List<Employee> selectEmps1(int[] ids);

EmployeeMapper.xml定义SQL语句

方式1:

select * from tb_employee where id in ( ? , ? , ? )

array:参数数组

<select id="selectEmps" resultType="Employee">
	select * from tb_employee where id in
    <foreach collection="array" open="(" item="id" separator="," close=")">
    	#{id}
    foreach>
select>

方式2:

select * from tb_employee where id=? or id=? or id=?

array:参数数组

<select id="selectEmps1" resultType="Employee">
	select * from tb_employee where
	<foreach collection="array" item="a" separator="or">
		id=#{a}
	foreach>
select>

TestMyBatis.java测试类

@Test
public void testSelect2(){
	int[] ids={2,4,6};
	List<Employee> employees = em.selectEmps1(ids);
	for (Employee employee : employees) {
		System.out.println(employee);
	}
}

4.6.2.批量删除

EmployeeMapper.java接口

//批量删除
int deleteBatch(List<Integer> ids);

EmployeeMapper.xml定义SQL语句

<delete id="deleteBatch">
	delete from tb_employee where id in
    <foreach collection="list" open="(" item="id" separator="," close=")">
    	#{id}
    foreach>
delete>

TestMyBatis.java测试类

@Test
public void testdeleteBatch(){
	List<Integer> ids=new ArrayList<Integer>();
	ids.add(1);
	ids.add(3);
	ids.add(5);
	int count = em.deleteBatch(ids);
	System.out.println(count);
}

4.6.3.批量增加

EmployeeMapper.java接口

//批量新增
int addBatch(List<Employee> employees);

EmployeeMapper.xml定义SQL语句

list:参数集合


<insert id="addBatch">
	insert into tb_employee values
	<foreach collection="list" separator="," item="e">
    	(null,#{e.name},#{e.email},#{e.gender},#{e.age})
    foreach>
insert>

TestMyBatis.java测试类

@Test
public void testAddaddBatch(){
	List<Employee> employees=new ArrayList<Employee>();
    employees.add(new Employee(null,"小琼1","[email protected]","0",18));
    employees.add(new Employee(null,"小琼2","[email protected]","0",18));
    employees.add(new Employee(null,"小琼3","[email protected]","0",18));
    employees.add(new Employee(null,"小琼4","[email protected]","0",18));
    employees.add(new Employee(null,"小琼5","[email protected]","0",18));
    int count = em.addBatch(employees);
    System.out.println(count);
}

4.7.SQL片段

封装服用思想:如果Maper.xml文件中有相同的SQL语句,那么没必要重复写,可以写成一个SQL片段,公用一下,减少代码量!

原始代码:

<select id="findEmpByNameAndGender" resultType="Employee">
	select * from tb_employee where 1=1
    <if test="name!=null and name!=''">
		and name = #{name}
	if>
	<if test="gender!=null and gender!=''">
		and gender = #{gender}
	if>
select>

<select id="findEmpByNameAndGenders" resultType="Employee">
	select * from tb_employee
	<where>
		<if test="name!=null and name!=''">
			and name = #{name}
		if>
		<if test="gender!=null and gender!=''">
			and gender = #{gender}
		if>
	where>
select>

封装复用后的代码:公有SQL片段

<!--公有SQL片段-->
<sql id="sqlFragment">
	<if test="name!=null and name!=''">
		and name = #{name}
	</if>
	<if test="gender!=null and gender!=''">
		and gender = #{gender}
	</if>
</sql>

<select id="findEmpByNameAndGender" resultType="Employee">
	select * from tb_employee where 1=1
	<include refid="sqlFragment"/>
</select>

<select id="findEmpByNameAndGenders" resultType="Employee">
	select * from tb_employee
	<where>
		<include refid="sqlFragment"/>
	</where>
</select>

4.8.Junit标准测试写法

@Before : 在**@Test测试方法之前**,自动优先运行@Before注解的方法

@Test : 单元测试

@After : 在**@Test测试方法之后**,自动运行@After注解的方法

package com.dream.test;

import com.dream.mapper.EmployeeMapper;
import com.dream.pojo.Employee;
import com.dream.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class TestMyBatis {


    SqlSession sqlSession = null;
    EmployeeMapper em = null;

    @Before
    public void init(){
        sqlSession = MyBatisUtils.getSqlSession();
        em = sqlSession.getMapper(EmployeeMapper.class);
    }

    @Test
    public void testFindEmpByNameAndGender(){
        Employee employee = new Employee();
        employee.setName("Jerry");
        employee.setGender("1");
        List<Employee> employees = em.findEmpByNameAndGender(employee);
        for (Employee e:employees) {
            System.out.println(e);
        }
    }

    @After
    public void destroy(){
        MyBatisUtils.closeSqlSession(sqlSession);
    }
}

5.MyBatis关联映射

关联是双向的

现实生活中实体和实体之间的关系:一对一 一对多 多对多

5.1.关联映射的作用

在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存着一对一一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表

MyBatis中是如何实现这种多表关系的映射呢?

答:用查询结果集标签****

resultMap元素是MyBatis中最重要最强大的元素。它就是让你远离90%的需要从结果集中取出数据的JDBC代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事情。

resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。

resultMap元素中,允许有以下直接子元素:

:作用与result相同,同时可以标识出这个字段值可以区分其他对象实例,可以理解为数据表中的主键,可以定位数据表中唯一的记录。

:将数据表中的字段注入到Java对象属性中。

:关联有一个关系,如"用户"有一个"账号",属性用javaType

:集合有很多关系,如"客户"有很多"订单",属性用ofType

这些子元素所包含的属性:

property:表示实体类定义的对象的名称

column:表示数据库表中的字段名称

javaType:association标签中使用的属性,代表一个关系

oftype:collection标签中使用的属性,代表很多关系集合

补充:selcet元素中有很多属性,常见的如下:

id:命名空间唯一标识,可以被用来引用这条语句。

parameterType:该属性参数是传入类的完全限定名或者别名

resultType:从这条语句要返回的期望类型的类的完全限定名或别名(这里应该注意的是集合的类型,应该是集合可以包含的类型,并不是集合本身)。

resultMap:引用外部的resultMap,其名称要和外部的resultMap元素的id名称一致,用于映射其结果到实体类指定对象中。

重要:resultType和resultMap不能同时使用

面试题1:我们什么时候使用resultType?什么时候使用resultMap?

答:(1) 当去查询一张表时,可以使用resultType,这种情况下,MyBatis会在底层自动创建一个resultMap,基于属性名来映射到JavaBean属性上;

​ (2) 在使用resultMap时,就必须在xml文件中写resultMap

5.2.一对一关联

一对一表查询

EmpMapper.java接口

//一对一表查询
//根据员工ID查询员工信息(包括该员工的工牌信息)
Emp findById(int id);

EmpMapper.xml定义SQL语句

这里取别名是为了防止column和property对应冲突

<select id="findById" resultMap="empMap">
	SELECT
    E.id 'eid',
    E.name,
    E.email,
    E.gender,
    E.age,
    C.*
    FROM tb_emp E INNER JOIN tb_card C ON E.id=C.emp_id
    WHERE C.id=#{id}
select>

<resultMap id="empMap" type="Emp">
	<id column="eid" property="id"/>
    <result column="name" property="name"/>
    <result column="email" property="email"/>
    <result column="gender" property="gender"/>
    <result column="age" property="age"/>
    
    <association property="card" javaType="Card">
    	<id column="id" property="id"/>
        <result column="card_no" property="cardNo"/>
        <result column="card_depart" property="cardDepart"/>
        <result column="card_job" property="cardJob"/>
        <result column="card_sal" property="cardSal"/>
        <result column="emp_id" property="empId"/>
    association>
resultMap>

TestEmp.java测试类

SqlSession sqlSession = null;
EmpMapper empMapper = null;

@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
empMapper = sqlSession.getMapper(EmpMapper.class);
}   

@Test
public void testFindById(){
	Emp emp = empMapper.findById(9);
	System.out.println(emp);
}

@After
public void destroy(){
	MyBatisUtils.closeSqlSession(sqlSession);
}

一对一反向表查询

EmpMapper.java接口

//一对一表反向查询
//根据员工的id查询工牌信息(包括该员工的员工信息)
Card findByCardId(int id);

EmpMapper.xml定义SQL语句

<select id="findByCardId" resultMap="cardMap">
	SELECT
    C.*,
    E.id 'eid',
    E.name,
    E.email,
    E.gender,
    E.age
    FROM tb_emp E INNER JOIN tb_card C ON E.id=C.emp_id
    WHERE C.id=#{id}
select>

<resultMap id="cardMap" type="Card">
	<id column="id" property="id"/>
    <result column="card_no" property="cardNo"/>
    <result column="card_depart" property="cardDepart"/>
    <result column="card_job" property="cardJob"/>
    <result column="card_sal" property="cardSal"/>
    <result column="emp_id" property="empId"/>
    <association property="emp" javaType="Emp">
    	<id column="eid" property="id"/>
        <result column="name" property="name"/>
    	<result column="email" property="email"/>
    	<result column="gender" property="gender"/>
    	<result column="age" property="age"/>
    association>
resultMap>

TestEmp.java测试类

@Test
public void testFindByCardId(){
	Card card = empMapper.findByCardId(3);
	System.out.println(card);
}

5.3.一对多关联

一对多查询

编写实体类Movie

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Movie {

  private Integer id;
  private String movieTitle;
  private String moviePrice;
  private String movieTime;
  private String movieActor;
  private Integer typeId;

  private MovieType movieType;

}

MovieMapper接口

//一对多查询
//查询电影信息(包含电影类别信息)
Movie findMovieById(int id);

MovieMapper.xml定义SQL语句

<select id="findMovieById" resultMap="movieMap">
	SELECT
    M.*,
    T.id 'tid',
    T.tname 
    FROM tb_type T INNER INTO tb_movie M ON T.ID=M.type_id 
    WHERE M.id=#{id}
select>

<resultMap id="movieMap" type="Movie">
	<id column="id" property="id"/>
    <result column="movie_title" property="movieTitle"/>
	<result column="movie_price" property="moviePrice"/>
	<result column="movie_time" property="movieTime"/>
	<result column="movie_actor" property="movieActor"/>
	<result column="type_id" property="typeId"/>
    <association property="movieType" javaType="MovieType">
    	<id column="tid" property="id"/>
        <result column="tname" property="tname"/>
    association>
resultMap>

TestMovie.java测试类

SqlSession sqlSession = null;
MovieMapper movieMapper = null;

@Before
public void init(){
	sqlSession = MyBatisUtils.getSqlSession();
	movieMapper =sqlSession.getMapper(MovieMapper.class);
}

@Test
public void testFindMovieById(){
	Movie movie = movieMapper.findMovieById(2);
	System.out.println(movie);
}

@After
public void destroy(){
	MyBatisUtils.closeSqlSession(sqlSession);
}

多对一查询

编写实体类MovieType

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MovieType {

  private Integer id;
  private String tname;

  private List<Movie> movies;
}

MovieMapper接口

//多对一查询
//根据电影类别ID查询点类别信息(包括该类别下所有的电影信息)
MovieType findMovieType(int movieType_id);

MovieMapper.xml定义SQL语句

<select id="findMovieType" resultMap="movieTypeMap">
	SELECT
    T.id 'tid',
    T.tname,
    M.* 
    FROM tb_type T INNER JOIN tb_movie M ON T.id=M.type_id 
    WHERE T.id=3
select>

<resultMap id="movieTypeMap" type="MovieType">
	<id column="tid" property="id"/>
    <result column="tname" property="tname"/>
    <collection property="movies" ofType="Movie">
    	<id column="id" property="id"/>
        <result column="movie_title" property="movieTitle"/>
		<result column="movie_price" property="moviePrice"/>
		<result column="movie_time" property="movieTime"/>
		<result column="movie_actor" property="movieActor"/>
		<result column="type_id" property="typeId"/>
    collection>
resultMap>

TestMovie.java测试类

@Test
public void testFindMovieType(){
	MovieType movieType = movieTypeMapper.findMovieType(1);
	System.out.println(movieType);
	List<Movie> movies = movieType.getMovies();
	for (Movie movie:movies) {
		System.out.println(movie);
	}
}

5.4.多对多关联

多对多查询

编写实体类Star

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Star {

    private Integer id;
    private String name;
    private String age;
    private String work;

    private List<Role> roles;
}

StarMapper接口

//多对多查询
//查询明星信息(包括该明星的角色信息)
Star findById(int id);

StarMapper.xml定义SQL语句

<select id="findById" resultMap="starMap">
	SELECT
    S.*,
    R.id 'rid',
    R.role_name,
    R.role_desc
    FROM tb_star S INNER JOIN tb_star_role SR ON S.id=SR.star.id 
    INNER JOIN tb_role R ON SR.role_id=R.id
    WHERE S.id=#{id}
select>

<resultMap id="starMap" type="Star">
	<id column="id" property="id"/>
    <result column="name" property="name"/>
	<result column="age" property="age"/>
	<result column="work" property="work"/>
	
	<collection property="roles" ofType="Role">
		<id column="rid" property="id"/>
		<result column="role_name" property="roleName"/>
		<result column="role_desc" property="roleDesc"/>
	collection>
resultMap>

TestStar.java测试

package com.dream.test;

import com.dream.mapper.StarMapper;
import com.dream.pojo.Emp;
import com.dream.pojo.Role;
import com.dream.pojo.Star;
import com.dream.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestStar {

    SqlSession sqlSession;
    StarMapper starMapper;

    @Before
    public void init(){
        sqlSession = MyBatisUtils.getSqlSession();
        starMapper = sqlSession.getMapper(StarMapper.class);
    }

    @Test
    public void testFindById(){
        Star star = starMapper.findById(1);
        System.out.println(star);
    }


    @After
    public void destroy(){
        MyBatisUtils.closeSqlSession(sqlSession);
    }
}

反向多对多查询

编写实体类Role

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {

    private Integer id;
    private String roleName;
    private String roleDesc;

    private List<Star> stars;
}

StarMapper接口

//反向多对多查询
//查询角色信息(包含该角色的明星信息)
Role findByRoleId(int id);

StarMapper接口

<select id="findByRoleId" resultMap="roleMap">
	SELECT 
    S.*,
    R.id 'rid',
    R.role_name,
    R.role_desc
    FROM tb_star S INNER JOIN tb_star_role SR ON S.id=SR.star_id 
    INNER JOIN tb_role R ON SR.role_id=R.id
    WHERE R.id=#{id}
select>

<resultMap id="roleMap" type="role">
	<id column="rid" property="id"/>
	<result column="role_name" property="roleName"/>
	<result column="role_desc" property="roleDesc"/>
    <collection property="stars" ofType="Star">
    	<id column="id" property="id"/>
    	<result column="name" property="name"/>
		<result column="age" property="age"/>
		<result column="work" property="work"/>
    collection>
resultMap>

TestStar.java测试

@Test
public void testFindRoleById(){
	Role role = starMapper.findByRoleId(1);
	System.out.println(role);
}

6.MyBatis缓存

6.1.Cache缓存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QwiLCkRq-1628923495030)(D:\Typora\图片\Cache缓存.png)]

面试题:缓存的一致性问题!
  • 缓存中有,先查询缓存。缓存中没有,那么查询数据库。这样的话不用每次都查询数据库。减轻数据库的压力。提高查询效率!!!
  • 第一次查询的时候,由于缓存中没有,那么去查询数据库返回给客户端。同时还会把这个次查询的数据放入缓存
  • 第二次查询同样的数据时候,发现缓存中曾经有查询过的数据,那么直接从缓存中读取。不必再次查询数据库,减轻数据库服务器压力,缓存中有就查缓存,缓存中没有就查数据库
  • 如果数据库中数据发生了修改,那么缓存就会清空,保持数据的一致性!防止脏数据

6.2.MyBatis缓存分析

​ mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBqCG43C-1628923495032)(D:\Typora\图片\MyBatis缓存.png)]

​ Mybatis的缓存,包括一级缓存和二级缓存。

一级缓存sqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。一级缓存是默认使用的

​ 理解:一级缓存指的就是sqlSession,在sqlsession中有一个数据区域,是Map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值;一级缓存中的value,就是查询出的结果对象

二级缓存mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的二级缓存需要配置,然后手动开启

​ 理解:二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个Map结构,这个区域就是二级缓存区域。二级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值;二级缓存中的value,就是查询出的结果对象

​ Map key:缓存标志 value:缓存的数据

6.3.一级缓存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8f3n3vH-1628923495033)(D:\Typora\图片\一级缓存示意图.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpGLK0yq-1628923495034)(D:\Typora\图片\一级缓存流程图.png)]

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中

​ 如果sqlSession去执行commit(执行插入更新删除),或者手动清除缓存(sqlSession.clearCache()),清空sqlSession中的一级缓存,这样做的目的是为了让缓存中存储的是新信息,避免脏读避免缓存的一致性问题

第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有直接从缓存中获取用户信息。

测试:

	String resource = "mybatis-config.xml"
    InputStream inputStream = Resources.getResourceAsStream(resource);
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	
	BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

	Book book01 = bookMapper.findById(1);第一次查询,先查询数据库,数据库查询到之后 再放入一级缓存中
	System.out.println(book01);

     //手动清除缓存
    //sqlSession.clearCache();

    //调用了增删改,那么缓存也会自动清空
    //Book book = new Book();
    //book.setId(2);
    //book.setTitle("斗罗大陆2");
    //int i = bookMapper.updateBook(book);
    //System.out.println("更新成功!");

    Book book02 = bookMapper.findById(1);//第二次查询同样的数据,那么直接查询缓存
    System.out.println(book02);
    sqlSession.close();

    System.out.println(book01==book02);

一级缓存默认自动打开的

关闭一级缓存

方式一:在核心配置文件mybatis-config.xml中加入以下内容(开启一级缓存总开关),在settings标签中添加以下内容:

	<settings>
        
        
        <setting name="localCacheScope" value="STATEMENT"/>
    settings>

方式二:

StudentMapper.xml映射文件中,加入以下内容(局部开启一级缓存)

开启一级缓存:flushCache=“false”(默认值)

	<select id="findById" resultType="Book" flushCache="false" useCache="true">
        select * from tb_book where id=#{id}
    select>

关闭一级缓存:flushCache=“true” 局部开关:刷新缓存,也就是关闭一级缓存!

	<select id="findById" resultType="Book" flushCache="true" useCache="true">
        select * from tb_book where id=#{id}
    select>

补充:

当为select语句时:

flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存

useCache默认为true,表示会将本条语句的结果进行二级缓存

当为insertupdatedelete语句时:

flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空

​ useCache属性在该情况下没有。

6.4.二级缓存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hsDm4wj-1628923495036)(D:\Typora\图片\二级缓存示意图.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFsYkXtS-1628923495037)(D:\Typora\图片\二级缓存流程图.png)]

第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内

第二次调用相同namespace下的mapper映射文件(xml)中相同的SQL去查询用户信息,会去对应的二级缓存内取结果。

​ 如果调用相同namespace下的mapper映射文件的增删改(SQL),并执行了commit操作,此时会清空该namespace下的二级缓存

测试:

		String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession01 = sqlSessionFactory.openSession(true);
        BookMapper bookMapper01 = sqlSession01.getMapper(BookMapper.class);

        Book book01 = bookMapper01.findById(1);
        System.out.println(book01);
        //sqlSession1需要此处关闭,因为sqlSession关闭,那么就放入二级缓存中
        sqlSession01.close();
        System.out.println("-----------------------------------------------");

        //中途执行增删改,二级缓存是否会失效(清空)
        //SqlSession sqlSession03 = sqlSessionFactory.openSession(true);
        //BookMapper bookMapper03 = sqlSession03.getMapper(BookMapper.class);

        //Book book = new Book();
        //book.setId(1);
        //book.setTitle("斗罗大陆2");
        //bookMapper03.updateBook(book);
        //sqlSession03.close();


        SqlSession sqlSession02 = sqlSessionFactory.openSession(true);
        BookMapper bookMapper02 = sqlSession02.getMapper(BookMapper.class);

        Book book02 = bookMapper02.findById(1);
        System.out.println(book02);
        sqlSession02.close();

        System.out.println(book01==book02);

如果第一个session没有提交,则没有进行二级缓存。所以,想实现二级缓存,需要前面的session已经提交过,并且相同的提交sql。

开启二级缓存

MyBatis默认是没有开启二级缓存的。

方式一:在核心配置文件mybatis-config.xml中加入以下内容(开启二级缓存总开关),在settings标签中添加以下内容:

	<settings>
        
        <setting name="cacheEnabled" value="true"/>
    settings>

方式二:在StudentMapper.xml映射文件中,加入以下内容(局部开启二级缓存),开启二级缓存:


<cache>cache>

设置cache标签的属性:

  • eviction缓存回收策略,默认值是LRU,有以下几种回收策略:
    • LRU最近最少回收,移除最长时间不被使用的对象
    • FIFO先进先出,按照缓存进入的顺序来移除它们
    • SOFT软使用,移除基于垃圾回收器状态和软引用规则的对象
    • WEAK弱使用,更积极的移除基于垃圾回收器和弱引用规则的对象
  • flushinterval缓存刷新间隔,缓存多长时间刷新一次,设置一个毫秒值,默认是不刷新的
  • readOnly是否只读
    • true只读。MyBatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据MyBatis为了加快获取数据,直接就会将数据在缓存中的引用交给用户;不安全,速度快
    • false读写(默认)。MyBatis认为不仅会有读取数据,还会有修改操作。会通过序列化和反序列化的技术克隆一份新的数据给用户
  • size缓存中的对象个数
  • type指定自定义缓存的全类名(实现Cache接口即可),自定义缓存或者整合第三方缓存时使用
  • blocking:若缓存中找不到对应的key,缓存会一直blocking,直到有对应的数据进入内存

补充:

​ 由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。

​ 缓存默认是存入内存中,但如果需要把缓存对象存入硬盘那么就需要序列化(实体类要实现)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ga1qio37-1628923495038)(D:\Typora\图片\自动实现序列化.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKdFjRqc-1628923495039)(D:\Typora\图片\序列化ID添加.png)]

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {


    private static final long serialVersionUID = 2344697603915147867L;

    private Integer id;
    private String title;
    private Double price;
    private String publish;
}

如果该类存在父类,那么父类也要实现序列化

禁用二级缓存

​ 该statement中设置userCache=“false”,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。

	<select id="findById" resultType="Book" flushCache="false" useCache="false">
        select * from tb_book where id=#{id}
    select>

你可能感兴趣的:(MyBatis,java-ee,maven,mybatis)