Mybatis


mybatis的官方文档

一、第一个Mybatis程序

Mybatis-01

  1. 搭建环境

建立数据库,然后搭建一个maven项目。
导入mybatis相关的jar包

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
  1. 获取sqlsession对象

先配置xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
<!-- 每一个Mapper.xml文件都需要在核心配置文件里面注册-->
    <mappers>
        <mapper resource="com/zyl/dao/userMapper.xml"/>
    </mappers>
</configuration>

然后从 XML 中构建 SqlSessionFactory

从 SqlSessionFactory 中获取 SqlSession

package com.zyl.util;

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 {
    private static SqlSessionFactory sqlSessionFactory;//提升作用域
    static {
        try {
            //获取sqlSessionFactory对象,下面三句话是固定写法
            String resource = "mybatis-config.xml";
            InputStream inputStream =
                    Resources.getResourceAsStream(resource);
            sqlSessionFactory = new
                    SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取SqlSession连接,类比之前的statement对象
    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
    // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}


  1. 编写代码
  • 实体类
package com.zyl.pojo;

//实体类
public class User {
    private int id; //id
    private String name; //姓名
    private String pwd; //密码

    //有参构造、无参构造
    //Getter、Setter   此处省略
}


  • 接口类
    这里不叫dao,交Mapper
package com.zyl.dao;

import com.zyl.pojo.User;

import java.util.List;

public interface UserMapper {
    List<User> selectUser();
}
  • 接口实现类
    不叫impl了,叫Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace  绑定一个指定的Mapper接口-->
<mapper namespace="com.zyl.dao.UserMapper">

<!--select 查询语句
id:对应要实现的方法
resultType:返回值的类型
-->
    <select id="selectUser" resultType="com.zyl.pojo.User">
        select * from user
    </select>
</mapper>

  • 测试

用junit测试

package com.zyl.dao;

import com.zyl.pojo.User;
import com.zyl.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
    public static void main(String[] args) {

    }
    @Test
    public void userTest(){
        //1. 获取sqlSession对象,通过工具类
        SqlSession sqlSession=MybatisUtils.getSession();
        try{
            //2. 执行sql
            UserMapper mapper=sqlSession.getMapper(UserMapper.class);
            List<User> users=mapper.selectUser();
            for (User user : users) {
                System.out.println(user);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }
}

  1. 每一个Mapper.xml文件都需要在核心配置文件里面注册

  2. 还有就是maven约定大于配置的问题,配置文件可能无法生效或者导出,所以在pom.xml中加入下面的代码。
    因为默认配置文件.xml文件是写在resource目录下的,现在Mapper.xml文件写在java目录下,没有生效。

<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
  1. 数据库连接的问题

因为本地数据库版本的问题,jdbc包应该改用下面的依赖

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

还有就是核心配置文件中url的设置

<property name="url"
   value="jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>

二、CRUD

核心配置文件和工具类都不要在变。
只要写接口Mapper和Mapper.xml两个文件就行了。


我遇到一个问题,就是insert,update,delete写int返回值就报错,写void就可以运行。
问题解决:不是全是select,对应改成insert。。。
在这里插入图片描述


每一个Mapper.xml都要在核心配置文件里面注册
在这里插入图片描述

还有就是增删改要提交事务????


具体就是三步

  1. 编写接口
  2. 写Mapper.xml文件
  3. 写测试类(增删改要提交事务

namespace

配置文件中namespace中的名称为对应Mapper接口或者Dao接口的完整包名,必须一致!

万能的Map

之前插入的话,传的参数是对象,要把对象的所有参数都写出来,麻烦。

字段或者参数过多,用map。(但是这玩意儿不正规)


接口

    //万能的Map
    int addUser2(Map<String,Object> map);

xml

<!-- Map
values里面传的是map中的键
-->
    <insert id="addUser2" parameterType="map">
        insert into user(id,name,pwd) values(#{userid},#{userName},#{password})
    </insert>

测试类

    @Test
    public void addUser2(){
        SqlSession sqlSession=MybatisUtils.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("userid",9);
        map.put("userName","zyl05");
        map.put("password","123456");
        int num=mapper.addUser2(map);
        sqlSession.commit();  //真的要提交事务
        sqlSession.close();
    }

模糊查询like

接口

    //模糊查询用户
    List<User> getUserLike(String val);

  1. java代码执行时传通配符

xml

<!--模糊查询用户-->
    <select id="getUserLike" resultType="com.zyl.pojo.User">
        select * from user where name like #{val}
    </select>

测试类

@Test
    public void getUserLike(){
        SqlSession sqlSession=MybatisUtils.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);

        List<User> userList=mapper.getUserLike("%李%");
        for (User user : userList) {
            System.out.println(user.getName());
        }

        sqlSession.close();
    }

  1. sql拼接使用通配符
<!--模糊查询用户-->
    <select id="getUserLike" resultType="com.zyl.pojo.User">
        select * from user where name like "%"#{val}"%"
    </select>
@Test
    public void getUserLike(){
        SqlSession sqlSession=MybatisUtils.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);

        List<User> userList=mapper.getUserLike("李");
        for (User user : userList) {
            System.out.println(user.getName());
        }

        sqlSession.close();
    }

但是第二种方法容易被sql注入,不推荐使用。

三、配置解析

Mybatis-02
在这里插入图片描述

3.1 核心配置文件

mybatis-config.xml 系统核心配置文件
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

注意元素节点的顺序!顺序不对会报错

3.2 environments元素

在这里插入图片描述

  • 配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定

  • 子元素 environment
    具体的一套环境,通过设置id进行区别,id保证唯一!

子元素节点:transactionManager - [ 事务管理器 ]
在这里插入图片描述

子元素节点:数据源(dataSource)
在这里插入图片描述

3.3 properties属性

第一步 ; 在资源目录下新建一个db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis-study?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=123456

第二步 : 将文件导入properties 配置文件

<configuration>
    <!--导入properties文件,默认使用db.properties里面的信息-->
    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="111111"/>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url"
                          value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

注意<properties resource="db.properties"/>这句话在核心配置文件中的位置是固定的,变动的话会出错。

可以直接引入外部配置文件
也可以在其中添加一些配置信息
如果两种方法有同一个字段,默认使用外部配置文件里面的字段。

3.4 typeAliases 别名

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
在这里插入图片描述

  1. 直接给实体类起别名
<typeAliases>
        <!--直接给实体类起别名-->
        <typeAlias type="com.kuang.pojo.User" alias="User"/>
        
    </typeAliases>
  1. 扫描包,包下的实体类默认的别名就是实体类名首字母小写
<typeAliases>
        <package name="com.zyl.pojo"/>
    </typeAliases>

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

3.5 settings设置

在这里插入图片描述
懒加载,缓存开启关闭,日志

3.6 Mappers映射器

  1. 方式一:引入资源方式
<mappers>
        <mapper resource="com/zyl/dao/userMapper.xml"/>
    </mappers>

这个方法最好,随便怎么搞

  1. 方式二:使用class文件绑定注册
<mappers>
<mapper class="com.zyl.dao.UserMapper"/>
</mappers>

在这里插入图片描述
3. 方式三:使用扫描包的方式完成绑定

<mappers>
<!--        <mapper resource="com/zyl/dao/userMapper.xml"/>-->
<!--        <mapper class="com.zyl.dao.UserMapper"/>-->
        <package name="com.zyl.dao"/>
    </mappers>

他的问题和二一样。

还是推荐使用方式一

生命周期和作用域

在这里插入图片描述

  1. SqlSessionFactoryBuilder
  • 他的作用在于创建 SqlSessionFactory,一旦创建就不再需要
  • 局部变量
  1. SqlSessionFactory
  • 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。

  • 因为MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期

  • SqlSessionFactory 的最佳作用域是应用作用域。

  • 用单例或静态单例实现

  1. SqlSession
  • 相当于Statement

  • SqlSession实例线程不安全。所以 SqlSession 的最佳的作用域是请求或方法作用域。

  • 用玩就关闭(finally),因为会占用资源。

四、ResultMap

Mybatis-03
解决属性名(实体类的属性)与字段名(数据库中表的字段)不一致的问题。

现在强行修改实体类的属性名。

package com.zyl.pojo;

//实体类
public class User {
    private int id; //id
    private String name; //姓名
    private String password; //密码,原来是pwd,改成password

    。。。
}

查询结果为null

数据库字段
在这里插入图片描述

实体类
在这里插入图片描述
接口

package com.zyl.dao;

import com.zyl.pojo.User;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    //根据id查询用户
    User getUserById(int id);
}

映射文件

    <select id="getUserById" parameterType="int" resultType="com.zyl.pojo.User">
        select * from user where id=#{id}
    </select>

测试

@Test
    public void getUserById(){
        SqlSession sqlSession=MybatisUtils.getSession();
        try{
            UserMapper mapper=sqlSession.getMapper(UserMapper.class);
            User user=mapper.getUserById(1);
            System.out.println(user.getPassword());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

结果
在这里插入图片描述


分析

select * from user where id = #{id} 可以看做
select id,name,pwd from user where id = #{id}

mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; 【自动映射】

ResultMap 结果集映射

UserMapper.xml进行修改

<!--结果集映射  type是使用的别名-->
    <resultMap id="UserMap" type="user">
<!--column数据库中的字段  property实体类中的属性  映射关系-->
        <result column="id" property="id"></result>
        <result column="name" property="name"></result>
        <result column="pwd" property="password"></result>
    </resultMap>
    
    <select id="getUserById" parameterType="int" resultMap="UserMap">
        select * from user where id=#{id}
    </select>

五、日志

5.1 日志工厂

数据库出现异常需要排错,使用日志比较好
在这里插入图片描述
SLF4J
LOG4J 【掌握】
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING 【掌握】
NO_LOGGING

在Mybatis中具体使用那一个日志输出,在设置settings中设定

配置是在核心配置文件里面配置,注意位置。

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

输出日志的结果
在这里插入图片描述

5.2 LOG4J

在这里插入图片描述

使用LOG4J和之前的标准日志工厂不一样,他需要导包。

  1. 导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
  1. 在resources文件夹里面写入配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下
面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. Setting设置日志实现
    <settings>
        <!--标准的日志工厂实现-->
        <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
        <setting name="logImpl" value="LOG4J"/>
    </settings>
  1. 测试


    //日志对象,参数为当前了的class
    //注意导包:org.apache.log4j.Logger
    static Logger logger = Logger.getLogger(UserMapperTest.class);
    @Test
    public void log4jTest(){
    
        logger.info("info:come into log4jTest");
        logger.debug("debug:come into log4jTest");
        logger.error("error:come into log4jTest");

    }

在这里插入图片描述

在这里插入图片描述

5.3 limit实现分页

为什么需要分页

在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

在这里插入图片描述

  1. 接口
//limit分页
    List<User> getUserLimit(Map<String,Object> map);
  1. Mapper.xml
<select id="getUserLimit" parameterType="map" resultType="user">
        select * from user limit #{startIndex},#{pageSize}
    </select>
  1. 测试
@Test
    public void getUserLimit(){
        SqlSession sqlSession=MybatisUtils.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("startIndex",0);
        map.put("pageSize",5);

        List<User> userList=mapper.getUserLimit(map);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

5.4 RowBounds实现分页

5.5 PageHelper

六、使用注解开发

Mybatis-04

Mybatis的注解比较弱鸡,稍复杂的sql语句注解就力不从心了。

6.1 面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准, 使得开发变得容易 , 规范性更好

定义和实现的分离。

6.2 使用注解开发

  1. 注解在接口上实现
public interface UserMapper {
    //获取全部的用户
    @Select("select * from user")
    List<User> getUsers();

}
  1. 在核心配置文件绑定接口
<!--绑定接口,用class绑定-->
    <mappers>
        <mapper class="com.zyl.dao.UserMapper"/>
    </mappers>
  1. 测试
@Test
    public void test(){
        SqlSession sqlSession= MybatisUtils.getSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);
        List<User> userList=mapper.getUsers();
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

6.3 注解原理

注解开发本质是反射。
底层是动态代理实现的。

可以打上断点来观察。
在这里插入图片描述

UserMapper mapper=sqlSession.getMapper(UserMapper.class);

这行代码拿到类的class对象,就可以获得类的所有信息,返回值,注解。。。

6.4 Mybatis执行流程(重要)没看完

在这里插入图片描述

6.5 注解实现crud

可以在工具类创建SqlSession实例时开启事务自动提交。
在这里插入图片描述

public interface UserMapper {
    //获取全部的用户
    @Select("select * from user")
    List<User> getUsers();

    //注意#{}里面取的值是@Param定义的值
    @Select("select * from user where id=#{id2}")
    User getUserById(@Param("id2") int id);

    @Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd})")
    int addUser(User user);

    @Update("update user set name=#{name},pwd=#{pwd} where id=#{id}")
    int updateUser(User user);

    @Delete("delete from user where id = #{id}")
    int deleteUser(@Param("id")int id);


}

6.6 @Param &&#{}和${}区别

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用@Param。
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。只能对基本类型使用。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

#{}和${}区别就好比PreparedStatement和Statement的区别,前者可以防止sql注入。

6.7 Lombok

使用步骤

  1. 先在idea上面下载lombok插件,安装完插件要重启idea
  2. 导入jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

  1. 注解说明
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)

@Date
无参构造,get,set,hashcode,equals,toString

在这里插入图片描述

七、多对一的处理

Mybatis-05
能使用外键吗

多对一的理解:
多个学生对应一个老师
如果对于学生这边,就是多对一的现象,即从学生这边关联一个老师!

首先搭建环境

数据库环境

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, "秦老师");

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, "小明", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, "小红", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, "小张", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, "小李", 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, "小王", 1);

基本项目环境搭建,添加实体类

public class Teacher {
	private int id;
	private String name;
}
public class Student {
	private int id;
	private String name;
	//多个学生可以是同一个老师,即多对一
	private Teacher teacher;
}

编写实体类对应的Mapper接口 【两个】

在这里插入图片描述
编写Mapper接口对应的 mapper.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.zyl.dao.StudentMapper">

</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.zyl.dao.StudentMapper">
</mapper>

按查询嵌套处理

  1. 给StudentMapper接口增加方法
//获取所有学生及对应老师的信息
public List<Student> getStudents();
  1. 编写对应的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.zyl.dao.StudentMapper">
    <!--
    需求:获取所有学生及对应老师的信息
    思路:
    1. 获取所有学生的信息
    2. 根据获取的学生信息的老师ID->获取该老师的信息
    3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般
    使用关联查询?
    1. 做一个结果集映射:StudentTeacher
    2. StudentTeacher结果集的类型为 Student
    3. 学生中老师的属性为teacher,对应数据库中为tid。
    多个 [1,...)学生关联一个老师=> 一对一,一对多
    4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查
    询
    -->
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <!--association关联属性 property属性名 javaType属性类型 column在多
        的一方的表中的列名-->
        <association property="teacher" column="tid" javaType="Teacher"
                     select="getTeacher"/>
    </resultMap>
    <!--
    这里传递过来的id,只有一个属性的时候,下面可以写任何值
    association中column多参数配置:
    column="{key=value,key=value}"
    其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的
    字段名。
    -->
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>
  1. 编写完毕去Mybatis配置文件中,注册Mapper!
  2. 注意
<resultMap id="StudentTeacher" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多的一方
的表中的列名-->
<association property="teacher" column="{id=tid,name=tid}"
javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段
名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id} and name = #{name}
</select>
  1. 测试
@Test
    public void testGetStudents(){
        SqlSession session = MybatisUtils.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> students = mapper.getStudents();
        for (Student student : students){
            System.out.println(
                    "学生名:"+ student.getName()
                            +"\t老师:"+student.getTeacher().getName());
        }
    }

按结果嵌套处理

除了上面这种方式,还有其他思路吗?
我们还可以按照结果进行嵌套处理;

  1. 接口方法编写
public List<Student> getStudents2();
  1. 编写对应的mapper文件
<!--
    按查询结果嵌套处理
    思路:
    1. 直接查询出结果,进行结果集的映射
    -->
    <select id="getStudents2" resultMap="StudentTeacher2" >
        select s.id sid, s.name sname , t.name tname
        from student s,teacher t
        where s.tid = t.id
    </select>
    <resultMap id="StudentTeacher2" type="Student">
    <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <!--关联对象property 关联对象在Student实体类中的属性-->
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
  1. 去mybatis-config文件中注入【此处应该处理过了】

  2. 测试

@Test
    public void testGetStudents2(){
        SqlSession session = MybatisUtils.getSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> students = mapper.getStudents2();
        for (Student student : students){
            System.out.println(
                    "学生名:"+ student.getName()
                            +"\t老师:"+student.getTeacher().getName());
        }
    }

小结

按照查询进行嵌套处理就像SQL中的子查询
按照结果进行嵌套处理就像SQL中的联表查询

八、一对多的处理

Mybatis-06

一对多的理解:
一个老师拥有多个学生
如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!

实体类编写

public class Student {
	private int id;
	private String name;
	private int tid;
}
public class Teacher {
	private int id;
	private String name;
	//一个老师多个学生
	private List<Student> students;
}

按结果嵌套处理

  1. TeacherMapper接口编写方法
//获取指定老师,及老师下的所有学生
public Teacher getTeacher(int id);
  1. 编写接口对应的Mapper配置文件
<!--
    思路:
    1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
    2. 对查询出来的操作做结果集映射
    1. 集合的话,使用collection!
    JavaType和ofType都是用来指定对象类型的
    JavaType是用来指定pojo中属性的类型
    ofType指定的是映射到list集合属性中pojo的类型。
    -->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid, s.name sname , t.name tname, t.id tid
        from student s,teacher t
        where s.tid = t.id and t.id=#{id}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="name" column="tname"/>
        <collection property="students" ofType="Student">
            <result property="id" column="sid" />
            <result property="name" column="sname" />
            <result property="tid" column="tid" />
        </collection>
    </resultMap>
  1. 将Mapper文件注册到MyBatis-config文件中
<mappers>
        <mapper resource="com/zyl/dao/TeacherMapper.xml"/>
    </mappers>
  1. 测试
@Test
    public void testGetTeacher(){
        SqlSession session = MybatisUtils.getSession();
        TeacherMapper mapper = session.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher(1);
        System.out.println(teacher.getName());
        System.out.println(teacher.getStudents());
    }
  1. 出现的问题
    资源过滤
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
@Test
public void testGetTeacher(){
	SqlSession session = MybatisUtils.getSession();
	TeacherMapper mapper = session.getMapper(TeacherMapper.class);
	Teacher teacher = mapper.getTeacher(1);
	System.out.println(teacher.getName());
	System.out.println(teacher.getStudents());
}

按查询嵌套处理

  1. TeacherMapper接口编写方法
public Teacher getTeacher2(int id);
  1. 编写接口对应的Mapper配置文件
<select id="getTeacher2" resultMap="TeacherStudent2">
        select * from teacher where id = #{id}
    </select>
    <resultMap id="TeacherStudent2" type="Teacher">
        <!--column是一对多的外键 , 写的是一的主键的列名-->
        <collection property="students" javaType="ArrayList"
                    ofType="Student" column="id" select="getStudentByTeacherId"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid = #{id}
    </select>

  1. 将Mapper文件注册到MyBatis-config文件中
  2. 测试
@Test
    public void testGetTeacher2(){
        SqlSession session = MybatisUtils.getSession();
        TeacherMapper mapper = session.getMapper(TeacherMapper.class);
        Teacher teacher = mapper.getTeacher2(1);
        System.out.println(teacher.getName());
        System.out.println(teacher.getStudents());
    }

小结

在这里插入图片描述

九、动态sql

Mybatis-07

动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句。但是可以在sql层面执行一些逻辑代码

我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, (choose, when, otherwise),trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

1.搭建环境(狂神笔记有,我就不写全了)

CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

主要就是核心配置文件,实体类pojo,dao层的BlogMapper和他的xml文件。

然后写个测试类把数据插进去。


有个注意点:
我的Blog实体类的有一个属性和数据库对应的那个字段名不一致

public class Blog {
    private String id;
    private String title;
    private String author;
    private String createTime;  //属性名和字段名不一致
    private int views;

在这里插入图片描述
核心配置文件里面做如下添加,数据库_和java的驼峰命名自动匹配。

<settings>
        <!--标准的日志工厂实现-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
<!--        <setting name="logImpl" value="LOG4J"/>-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

1.where

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

2.set

上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

set和where很像。
where根据具体参数删除and或者where
set也是根据具体的参数动态删除逗号。

  1. 编写接口方法
int updateBlog(Map map);
  1. sql
<!--注意set是用的逗号隔开-->
    <update id="updateBlog" parameterType="map">
        update blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
        where id = #{id};
    </update>
  1. 测试
 @Test
    public void testUpdateBlog(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("title","动态SQL");
        map.put("author","秦疆");
        map.put("id","9d6a763f5e1347cebda43e2a32687a77");
        mapper.updateBlog(map);
        session.close();
    }

2.5 trim

trim可以定制化where和set
在这里插入图片描述
在这里插入图片描述

3. IF语句

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询。

  1. 编写接口方法
//需求1
    List<Blog> queryBlogIf(Map map);
  1. 编写sql语句
<!--需求1:
    根据作者名字和博客名字来查询博客!
    如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
    select * from blog where title = #{title} and author = #{author}
    -->
    <select id="queryBlogIf" parameterType="map" resultType="blog">
        select * from blog where 1=1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>

上面的这种写法不行

修改成带where标签的。
如果只匹配author的话他会自动去掉and。
如果title和author一个都没匹配会自动去掉where。

<select id="queryBlogIf" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <if test="title != null">
                title = #{title}
            </if>
            <if test="author != null">
                and author = #{author}
            </if>
        </where>
    </select>
  1. 测试

注意map里面可以不put title和author,什么都不put的话就是查询全部,put的话就是按put的条件查询。

@Test
    public void queryBlogIf(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, String> map = new HashMap<String, String>();
        //map.put("title","Mybatis如此简单");
        //map.put("author","狂神说");
        List<Blog> blogs = mapper.queryBlogIf(map);
        System.out.println(blogs);
        session.close();
    }

4. choose语句

相当于switch-case

  1. 编写接口方法
//choose
    List<Blog> queryBlogChoose(Map map);
  1. sql语句
<select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <choose>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = #{views}
                </otherwise>
            </choose>
        </where>
    </select>
  1. 测试

测试就是把三个put注释看怎么走sql

@Test
public void testQueryBlogChoose(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Java如此简单");
map.put("author","狂神说");
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}

5. sql片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

<sql id="if-title-author">
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
</sql>
<select id="queryBlogIf" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace-->
            <include refid="if-title-author"></include>
            <!-- 在这里还可以引用其他的 sql 片段 -->
        </where>
    </select>

6. foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

十、缓存

Mybatis-08

简介

查询要连接数据库,比较耗费资源。
一次查询的结果可以放在内存了,要再次查询相同的数据的时候就直接在内存里面取。

只有读做缓存。

  1. 什么是缓存 [ Cache ]?
  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  1. 为什么使用缓存?
    减少和数据库的交互次数,减少系统开销,提高系统效率。
  2. 什么样的数据能使用缓存?
    经常查询并且不经常改变的数据。

Mybatis缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

一级缓存

一级缓存也叫本地缓存:

与数据库同一次会话期间查询到的数据会放在本地缓存中。放在sqlsession中。

在这里插入图片描述

以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

一级缓存测试

  1. 在核心配置文件里面添加日志功能
    <settings>
        <!--标准的日志工厂实现-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
  1. 编写接口的方法
//根据id查询用户
User queryUserById(@Param("id") int id);
  1. 接口对应的xml文件
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>
  1. 测试
@Test
public void testQueryUserById(){
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	User user = mapper.queryUserById(1);
	System.out.println(user);
	User user2 = mapper.queryUserById(1);
	System.out.println(user2);
	System.out.println(user==user2);
	session.close();
}
  1. 结果分析

在这里插入图片描述

一级缓存失效

一级缓存失效有4种情况。

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1.SqlSession对象不同

@Test
    public void testQueryUserById1(){
        SqlSession session = MybatisUtils.getSession();
        SqlSession session2 = MybatisUtils.getSession();

        UserMapper mapper = session.getMapper(UserMapper.class);
        UserMapper mapper2 = session2.getMapper(UserMapper.class);

        User user = mapper.queryUserById(1);
        System.out.println(user);

        User user2 = mapper2.queryUserById(1);
        System.out.println(user2);
        
        System.out.println(user==user2);
        session.close();
        session2.close();
    }

2.SqlSession对象相同,但是查询的数据不同

@Test
public void testQueryUserById(){
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	UserMapper mapper2 = session.getMapper(UserMapper.class);
	User user = mapper.queryUserById(1);
	System.out.println(user);
	User user2 = mapper2.queryUserById(2);
	System.out.println(user2);
	System.out.println(user==user2);
	session.close();
}

3.sqlSession相同,两次查询之间执行了增删改操作!

@Test
public void testQueryUserById(){
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	User user = mapper.queryUserById(1);
	System.out.println(user);
	HashMap map = new HashMap();
	map.put("name","kuangshen");
	map.put("id",4);
	mapper.updateUser(map);
	User user2 = mapper.queryUserById(1);
	System.out.println(user2);
	System.out.println(user==user2);
	session.close();
}

4.sqlSession相同,手动清除一级缓存

@Test
public void testQueryUserById(){
	SqlSession session = MybatisUtils.getSession();
	UserMapper mapper = session.getMapper(UserMapper.class);
	User user = mapper.queryUserById(1);
	System.out.println(user);
	session.clearCache();//手动清除缓存
	User user2 = mapper.queryUserById(1);
	System.out.println(user2);
	System.out.println(user==user2);
	session.close();
}

二级缓存

  1. 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  2. 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  3. 工作机制
  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

使用步骤

  1. 开启全局缓存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
  1. 去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】
<cache/>
官方示例=====>查看官方文档
<cache
	eviction="FIFO"
	flushInterval="60000"
	size="512"
	readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

  1. 代码测试
所有的实体类先实现序列化接口
@Test
    public void testQueryUserByIdTWO(){
        SqlSession session = MybatisUtils.getSession();
        SqlSession session2 = MybatisUtils.getSession();

        UserMapper mapper = session.getMapper(UserMapper.class);
        UserMapper mapper2 = session2.getMapper(UserMapper.class);

        User user = mapper.queryUserById(1);
        System.out.println(user);

        session.close();
        User user2 = mapper2.queryUserById(1);
        System.out.println(user2);
        System.out.println(user==user2);
        session2.close();
    }

缓存原理

在这里插入图片描述

先查二级缓存,找不到查一级缓存,找不到查数据库。
每一个mapper的二级缓存放一起。
一级缓存是放在每个SqlSession对象里面,已关闭一级缓存就没了,但是会把一级缓存里面的内容提交到对应mapper的二级缓存里面。

同时增删改会刷新缓存,但是也可以设置增删改不刷新缓存
在这里插入图片描述

自定义缓存EhCache

第三方缓存实现–EhCache: 查看百度百科

  1. Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;
  2. 要在应用程序中使用Ehcache,需要引入依赖的jar包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
  1. 在mapper.xml中使用对应的缓存即可
<mapper namespace =org.acme.FooMapper>
<cache type =org.mybatis.caches.ehcache.EhcacheCache/>
</mapper>
  1. 编写ehcache.xml文件,如果在 加载时 未找到 /ehcache.xml 资源或出现问题,则将使用默认配置。

十一、总结

每一个实体类对应一个接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值