You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

49 KiB

title author top cover toc mathjax summary tags categories reprintPolicy abbrlink date coverImg img password
Mybatis TianZD true true true false Mybatis框架学习笔记,粗略学了一下,没有参考价值 [Mybatis Java 数据库 学习笔记] [java] cc_by da3e1361 2022-04-29 11:05:46 <nil> <nil> <nil>

[toc]

Mybatis

1、简介

Mybatis

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

获取mybatis

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库、io文件可以进行持久化

为什么需要持久化:有一些对象不能丢失

持久层

Dao层、Service层、Controller层

2、入门

第一个Mybatis程序

**思路:**搭建环境==》导入Mybatis==》编写代码==》测试

搭建环境

  1. 搭建数据库
CREATE DATABASE mybatis;
CREATE TABLE `user`(
	`id` INT(20) NOT NULL,
    `name` VARCHAR(30) DEFAULT NULL,
    `pwd` VARCHAR(30) DEFAULT NULL,
    PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;
  1. 新建maven项目
    1. 普通maven项目
    2. 删除src目录,使其变为父工程
    3. 导入依赖
<!--导入依赖-->
<dependencies>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

创建模块

由于在上述步骤中配置了父工程,并且父工程已经导入了依赖,因此所有的子模块不需要再次引入依赖

子模块的xml配置文件中多了<parent>

<parent>
    <artifactId>MybatisStudy</artifactId>
    <groupId>org.example</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>

编写mybatis核心配置文件,在resource目录下创建mybaits-config.xml

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。

<?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?useSSl=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
	<mappers>
    <mapper resource="com/tian/dao/UserMapper.xml"/>
	</mappers>
</configuration>

编写mybatis工具类

  • 从xml配置中获取 SqlSessionFactory 实例

  • 从 SqlSessionFactory 中获取 SqlSession

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

SqlSession中包括了操作数据库的方法

public class MybatisUtils {
   private static SqlSessionFactory sqlSessionFactory = null;
   static {
      try {
         /*以下三句话是固定的
         用来从xml配置中获取sqlSessionFactory对象
         * 1. 加载配置文件,maven中可以直接读取到resource中的配置文件
         * 2. 获取输入流实例
         * 3. 从XML配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例*/
         String resource = "mybatis-config.xml";
         InputStream inputStream = Resources.getResourceAsStream(resource);
         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
   
   /**
   * @Description: 从 SqlSessionFactory对象 中获取 SqlSession实例
   * @Author: TianZD
   * @Date: 2021/8/6 22:01
   * @Param: [] 
   * @Return: org.apache.ibatis.session.SqlSession
   */
   public static SqlSession getSqlSession() {
      return sqlSessionFactory.openSession();
   }

}

编写代码

  • 实体类
package com.tian.pojo;

/**
 * @program: MybatisStudy
 * @description: 实体类
 * @author: TianZD
 * @create: 2021-08-06 22:04
 **/
public class User {
	private int id;
	private String name;
	private String pwd;

	public User() {
	}

	public User(int id, String name, String pwd) {
		this.id = id;
		this.name = name;
		this.pwd = pwd;
	}

	public int getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	@Override
	public String toString() {
		return "User{" +
				"id=" + id +
				", name='" + name + '\'' +
				", pwd='" + pwd + '\'' +
				'}';
	}
}
  • Dao接口
package com.tian.dao;

import com.tian.pojo.User;

import java.util.List;

/**
 * @program: MybatisStudy
 * @description: Dao接口,后面用mapper代替,两者等价
 * @author: TianZD
 * @create: 2021-08-06 22:07
 **/
public interface UserDao {
	//查询全部
	List<User> getUserList();

	//根据id查询
	User getUserById(int id);

	//insert插入
	int addUser(User user);

	//update修改
	int updateUser(User user);

	//删除用户
	int deleteUser(int id);
}
  • 接口实现类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绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.tian.dao.UserDao">
    <!--查询语句
    id对应Dao/Mapper接口中的方法
    第二个属性使用resultType或者resultMap
    resultType对应sql语句返回的结果类型,这里对应User对象,使用全限定名-->
    <select id="getUserList" resultType="com.tian.pojo.User">
        select * from mybatis.user
    </select>

    <!--根据id查询-->
    <select id="getUserById" parameterType="int" resultType="com.tian.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>

    <!--insert插入-->
    <insert id="addUser" parameterType="com.tian.pojo.User">
        insert into mybatis.user (id, name, pwd)
        values (#{id}, #{name}, #{pwd});
    </insert>

    <!--修改update-->
    <update id="updateUser" parameterType="com.tian.pojo.User">
        update mybatis.user
        set name = #{name},
            pwd  = #{pwd}
        where id = #{id};
    </update>
    
    <!--删除-->
    <delete id="deleteUser" parameterType="int">
        delete
        from mybatis.user
        where id = #{id};
    </delete>

</mapper>
  • Junit测试
package com.tian.dao;

import com.tian.pojo.User;
import com.tian.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * @program: MybatisStudy
 * @description: 测试
 * @author: TianZD
 * @create: 2021-08-06 22:56
 **/
public class UserDaoTest {
	@Test
	public void test(){
		//1. 获得SqlSession对象
		SqlSession sqlSession = MybatisUtils.getSqlSession();
		//2. 执行sql、获取结果、输出
		//方式1,getMapper
		UserDao userDao = sqlSession.getMapper(UserDao.class);
		List<User> userList = userDao.getUserList();
		for (User user : userList) {
			System.out.println(user);
		}

		//3. 关闭SqlSession
		sqlSession.close();
	}

	@Test
	public void getUserById(){
	//	1. 获得SqlSession对象
		SqlSession sqlSession = MybatisUtils.getSqlSession();
	//	2. 执行sql
		UserDao mapper = sqlSession.getMapper(UserDao.class);
		User userById = mapper.getUserById(2);
		System.out.println(userById);
	//	3. 关闭
		sqlSession.close();
	}

	@Test
	public void addUserTest(){
	//	1.从配置类中获得SqlSession对象
		SqlSession sqlSession = MybatisUtils.getSqlSession();
	//	2.sql语句
		UserDao mapper = sqlSession.getMapper(UserDao.class);
		int tian4 = mapper.addUser(new User(4, "tian4", "123456"));
		if (tian4 > 0) {
			System.out.println("插入成功");
		}

	//	增删改需要提交事务
		sqlSession.commit();
	//	3. 关闭
		sqlSession.close();
	}

	@Test
	public void updateUserTest() {
	//	1.获取sqlsession对象
		SqlSession sqlSession = MybatisUtils.getSqlSession();
	//	2.sql
		UserDao mapper = sqlSession.getMapper(UserDao.class);
		int tian4 = mapper.updateUser(new User(4, "tian4", "123123"));
	//	增删改需要提交事务
		sqlSession.commit();
	//	3.关闭
		sqlSession.close();
	}

	@Test
	public void deleteUserTest() {
		//	1.获取sqlsession对象
		SqlSession sqlSession = MybatisUtils.getSqlSession();
		//	2.sql
		UserDao mapper = sqlSession.getMapper(UserDao.class);
		int i = mapper.deleteUser(4);
		//	增删改需要提交事务
		sqlSession.commit();
		//	3.关闭
		sqlSession.close();
	}
}

总结

在写完上述以后,后续使用步骤:

  1. 在UserDao接口中增加相应的方法
  2. 在UserMapper.xml中增加相应的sql语句
  3. 在测试方法中增加相应的测试方法

注意:增删改需要在关闭之前提交事务

sqlSession.commit();

可能错误:

  • org.apache.ibatis.binding.BindingException: Type interface com.tian.dao.UserDao is not known to the MapperRegistry.

在mybatis.config.xml文件中没有配置mapper.xml

增加如下:

注意,路径用斜杠隔开

<mappers>
    <mapper resource="com/tian/dao/UserMapper.xml"/>
</mappers>
  • 错误2:

Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/tian/dao/UserMapper.xml

资源过滤原因

maven由于约定大于配置,可能遇到配置文件无法被导出或者生效的问题,解决方案:

在父工程pop.xml中配置resource,防治资源导出失败

<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

生命周期和作用域

作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题

image-20210807175425111

SqlSessionFactoryBuilder

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 最佳作用域是方法作用域(也就是局部方法变量)

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

  • 说白了可以想象为:数据库连接池
  • 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • 连接到连接池的一个请求
  • 最佳的作用域是请求或方法作用域
  • 用完之后需要关闭,否则造成资源被占用

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

image-20210807175744673

每一个Mapper代表一个具体的业务

CRUD

namespace

namespace中的包名要和Dao/Mapper接口的包名保持一致

<mapper namespace="com.tian.dao.UserDao">

绑定时的包名用.不能用/

select

选择、查询语句

  1. 编写接口
   //查询全部
   List<User> getUserList();

   //根据id查询
   User getUserById(int id);
  1. 编写对应的mapper中的sql语句
<!--查询语句
id对应Dao/Mapper接口中的方法
第二个属性使用resultType或者resultMap
resultType对应sql语句返回的结果类型,这里对应User对象,使用全限定名-->
<select id="getUserList" resultType="com.tian.pojo.User">
    select * from mybatis.user
</select>

<!--根据id查询-->
<select id="getUserById" parameterType="int" resultType="com.tian.pojo.User">
    select * from mybatis.user where id = #{id}
</select>
  1. 编写测试
@Test
public void test(){
   //1. 获得SqlSession对象
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   //2. 执行sql、获取结果、输出
   //方式1,getMapper
   UserDao userDao = sqlSession.getMapper(UserDao.class);
   List<User> userList = userDao.getUserList();
   for (User user : userList) {
      System.out.println(user);
   }

   //3. 关闭SqlSession
   sqlSession.close();
}

@Test
public void getUserById(){
// 1. 获得SqlSession对象
   SqlSession sqlSession = MybatisUtils.getSqlSession();
// 2. 执行sql
   UserDao mapper = sqlSession.getMapper(UserDao.class);
   User userById = mapper.getUserById(2);
   System.out.println(userById);
// 3. 关闭
   sqlSession.close();
}

insert

增删改需要提交事务

  1. 编写接口
  2. mapper中的sql语句
<!--insert插入-->
<insert id="addUser" parameterType="com.tian.pojo.User">
    insert into mybatis.user (id, name, pwd)
    values (#{id}, #{name}, #{pwd});
</insert>
  1. 测试:增删改需要提交事务
//  增删改需要提交事务
   sqlSession.commit();

update

  1. 编写接口
  2. sql语句
<!--修改update-->
<update id="updateUser" parameterType="com.tian.pojo.User">
    update mybatis.user
    set name = #{name},
        pwd  = #{pwd}
    where id = #{id};
</update>
  1. 测试:需要提交事务

Delete

  1. 接口
  2. sql语句
<!--删除-->
<detete id="deleteUser" parameterType="int">
    delete
    from mybatis.user
    where id = #{id};
</delete>
  1. 测试:需要提交事务

使用Map传参

假如我们的实体类或者数据库中的表、字段或者参数过多,我们应当考虑使用Map

使用User对象时,假如需要修改密码,当字段过多时,sql语句中还需要把其他字段给加上,很麻烦

使用map时:

  1. 接口
int updateUser2(Map<String, Object> map);
  1. sql语句

sql语句中传入的参数类型为map

具体传入的参数不需要和数据库以及实体类中的对应,在map.put()中进行对应即可

<!--使用map-->
<update id="updateUser2" parameterType="map">
    update mybatis.user
    set pwd = #{userPwd}
    where id = #{userId};
</update>
  1. 测试
@Test
public void updateUser2Test() {
   // 1.获取sqlsession对象
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   // 2.sql
   UserDao mapper = sqlSession.getMapper(UserDao.class);

   // map
   Map<String, Object> map = new HashMap<>();
   map.put("userId", 3);
   map.put("userPwd", "000000");

   int i = mapper.updateUser2(map);

   // 增删改需要提交事务
   sqlSession.commit();
   // 3.关闭
   sqlSession.close();
}

模糊查询

模糊查询需要防止sql注入

  1. java代码执行的时候,传入通配符% %
List<User> u = mapper.getUserLike("%tian%");
  1. mapper中的sql语句使用where 字段 like 加%,会导致sql注入
select * from mybatis.user where name like "%"#{value}"%"

3、配置解析

核心配置文件mybatis-config.xml

配置文件包含了会深深影响Mybatis行为的设置和属性信息

  • 属性properties
  • 设置settings
  • 类型别名typeAliases
  • 环境配置environments
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
  • 映射器mappers
  • 了解
    • 类型处理器typeHandlers
    • 对象工厂objectFactory
    • 插件plugins
    • 数据库厂商标识databaseProvider

环境配置environments

	<environments default="development">
        <!--通过id选择默认的事务管理器,默认jdbc,还有一个是MANAGED-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置数据源,UNPOOLED|POOLED|JNDI,默认采用的数据源,POOLED-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF8&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

MyBatis 可以配置成适应多种环境,通过id选择使用哪一个

每个 SqlSessionFactory 实例只能选择一种环境

  • 事务管理器

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"),默认使用JDBC:

  • 数据源

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"),默认使用pooled

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

连接数据库,数据库连接池:dbcp、c3p0、druid,用完会回收

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用

属性properties

通过properties属性涉嫌引用配置文件

属性都是可以外部配置且可以动态替换的,既可以在典型的java属性文件中配置,也可可以通过properties元素的子元素来传递,【db.properties】

通过外部配置db.properties

  1. 编写配置文件:db.properties:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
  1. xml中引入配置文件

在xml中,所有的标签都可以规定其顺序,properties=》settings=》typerAliases。。properties只能放在最上面

  • 中间可以设置属性,设置用户名和密码
  • 如果两个设置的属性中和db.properties中有同一个字段,优先使用外部的db.properties
<!--引入外部配置文件-->
<properties resource="db.properties">
</properties>
  1. 在整个配置文件中用来替换需要动态配置的属性值
<dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

类型别名typerAliases

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<!--别名-->
<typeAliases>
    <typeAlias type="com.tian.pojo.User" alias="User"></typeAlias>
</typeAliases>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author

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

使用com.tian.pojo.User时只需要用user即可

在实体类比较少的时候,使用第一种,实体类多的时候使用第二种,第一种可以自定义别名,第二种可以通过在实体类上增加注解来自定义别名,如下:

@Alias("newName")
public class User{
    ...
}

设置settings

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为

cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

其他配置

  • 类型处理器typeHandlers
  • 对象工厂objectFactory
  • 插件plugins
    • mybatis-generator-core
    • mybatis-plus:一个增强工具,简化mybatis
    • 通用mapper
  • 数据库厂商标识databaseProvider

映射器mappers

MapperRegistry:注册绑定我们的Mapper文件

MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

maven中resources文件夹下的文件在编译后,都放在了根目录下

方式1 :推荐使用

<!-- 使用相对于类路径的资源引用 -->
    <mappers>
        <mapper resource="com/tian/dao/UserMapper.xml"/>
    </mappers>

方式2:使用class文件

注意

  • 接口和他的Mapper配置文件必须同名
  • 接口和他的Mapper配置文件必须在同一个包下(Mapper配置文件可以在maven的resources目录下)
<!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
        <mapper class="com.tian.dao.UserMapper"/>
    </mappers>

方式3:使用扫描包进行绑定注入

注意:

  • 接口和他的Mapper配置文件必须同名
  • 接口和他的Mapper配置文件必须在同一个包下
<!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
        <package name="com.tian.dao"/>
    </mappers>

4、解决属性名和字段名不一致

问题

如:数据库中的字段名为pwd,实体类中的属性名为password

User:

public class User {
   private int id;
   private String name;
   private String password;
    ...
}

Mapper:

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

test:

@Test
public void getUserByIdTes() {
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   User userById = mapper.getUserById(2);
   System.out.println(userById);
}

查询得到输出:

User{id=2, name='tian2', password='null'}

分析:

由于数据库中的字段为pwd,在测试查询时,传入的参数为password,导致查询不到

解决方法:

  • 起别名

在mapper中修改sql语句:

<select id="getUserById" parameterType="int" resultType="com.tian.pojo.User">
    select id,name,pwd as password
    from mybatis.user
    where id = #{id};
</select>
  • resultMap

resultMap

结果集映射

  • resultMap元素是Mybatis中最重要最强大的元素
  • 设计思想是:对于简单的语句根本不需要配置显式的结果集映射,对于复杂的语句,只需要描述他们的关系就可以了

简单的结果集映射

<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <!--column:数据库中的字段,property:实体类中的属性-->
    <result column="pwd" property="password"/>
</resultMap>
<!--根据id查询-->
<select id="getUserById" resultMap="UserMap">
    select * from mybatis.user where id = #{id}
</select>

复杂

一对多,多对一

5、日志

日志工厂

介绍

如果一个数据库操作出现了异常,需要排错,需要日志

曾经用:sout、debug

现在:日志工厂

mybatis提供的:

  • SLF4J

  • LOG4J

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING

  • NO_LOGGING

具体使用哪一个,在mybatis-config.xml中的settings中设置,默认不使用

image-20210807195224236

标准的日志工厂实现

使用STDOUT_LOGGING不需要导包

  • 在mybatis-config.xml中配置日志
<settings>
    <!--name必须为logImpl,I为大写,value需要使用mybatis提供的哪些,不能有空格-->
            <!--标准的日志工厂实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • 配置后输出:
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Class not found: org.jboss.vfs.VFS
JBoss 6 VFS API is not available in this environment.
Class not found: org.jboss.vfs.VirtualFile
VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment.
Using VFS adapter org.apache.ibatis.io.DefaultVFS
Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo
Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo
Reader entry: User.class
Listing file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo
Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo/User.class
Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo/User.class
Reader entry: ����   < :
Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo
Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo
Reader entry: User.class
Listing file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo
Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo/User.class
Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo/User.class
Reader entry: ����   < :
Checking to see if class com.tian.pojo.User matches criteria [is assignable to Object]
Checking to see if class com.tian.pojo.User matches criteria [is assignable to Object]
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 1446983876.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@563f38c4]
==>  Preparing: select * from mybatis.user where id = ?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, tian2, 1234567
<==      Total: 1
User{id=2, name='tian2', password='1234567'}

Log4j

:需要导包

什么是log4j

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用log4j

  1. 导包
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. 新增配置文件log4j.properties

网上找即可

#将等级为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/tian.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. 配置log4j为日志的实现
<settings>
    <!--name必须为logImpl,I为大写,value需要使用mybatis提供的哪些,不能有空格-->
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. log4j使用
  • 在要使用log4j的类中,导入包import org.apache.log4j.Logger
  • 获取日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserDaoTest.class);
  • 编写测试代码

日志级别:info\debug\error

package com.tian.dao;

import org.apache.log4j.Logger;
import org.junit.Test;

/**
 * @program: MybatisStudy
 * @description: 测试
 * @author: TianZD
 * @create: 2021-08-06 22:56
 **/
public class UserDaoTest {
	static Logger logger = Logger.getLogger(UserDaoTest.class);
	@Test
	public void testLog4j() {
		//不同的级别
		logger.info("info:进入了testLog4j方法");
		logger.debug("debug:进入了testLog4j方法");
		logger.error("error:进入了testLog4j方法");
	}
}
  • 输出

在控制台和配置的输出文件中会输出以下:

[com.tian.dao.UserDaoTest]-info:进入了testLog4j方法
[com.tian.dao.UserDaoTest]-debug:进入了testLog4j方法
[com.tian.dao.UserDaoTest]-error:进入了testLog4j方法

6、分页

为什么要分页

减少数据的处理量

limit分页

SELECT * FROM user LIMIT startIndex,pageSize;

使用mybatis分页

核心sql

  1. 接口
//  分页查询
List<User> getUserByLimit(Map<String, Integer> map);
  1. Mapper.xml
<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <!--column:数据库中的字段,property:实体类中的属性-->
    <result column="pwd" property="password"/>
</resultMap>
<!--分页查询-->
<select id="getUserByLimit" resultMap="UserMap">
    select *
    from mybatis.user
    limit #{startIndex},#{pageSize};
</select>
  1. 测试
package com.tian.dao;

import com.tian.pojo.User;
import com.tian.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;

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

/**
 * @program: MybatisStudy
 * @description: 测试
 * @author: TianZD
 * @create: 2021-08-06 22:56
 **/
public class UserDaoTest {
   static Logger logger = Logger.getLogger(UserDaoTest.class);
   @Test
   public void TestLimit() {
      //1. 获取sqlsession对象
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      //2. sql, 获取mapper
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      //2.1 map
      Map<String, Integer> map = new HashMap<>();
      map.put("startIndex", 1);
      map.put("pageSize", 2);
      //2.2 sql
      List<User> userByLimit = mapper.getUserByLimit(map);
      for (User user : userByLimit) {
         logger.info(user);
      }
      //3. 关闭
      sqlSession.close();
   }
}

7、使用注解开发(mybatis不推荐)

面向接口编程

三个面向

面向过程:考虑问题时,以一个具体的流程为单位,考虑它的实现

面向对象:考虑问题时,以对象为单位,考虑他的属性和方法

面向接口:更多体现的是对系统整体的架构

理解

接口:应是定义(规范、约束)与实现(名实分离的原则)的分离,本身反映了系统设计人员对系统的抽象理解

面向过程编程(Procedure Oriented、简称PO面向对象编程(Object Oriented、简称OO 我们一定听过,然而实际企业级开发里受用更多的一种编程思想那就是:面向接口编程(Interface-Oriented

接口这个概念我们一定不陌生,实际生活中最常见的例子就是:插座!

我们只需要事先定义好插座的接口标准,各大插座厂商只要按这个接口标准生产,管你什么牌子、内部什么电路结构,这些均和用户无关,用户拿来就可以用;而且即使插座坏了,只要换一个符合接口标准的新插座,一切照样工作!

同理,实际代码设计也是这样!

我们在设计一个软件的代码架构时,我们都希望事先约定好各个功能的接口(即:约定好接口签名和方法),实际开发时我们只需要实现这个接口就能完成具体的功能!后续即使项目变化、功能升级,程序员只需要按照接口约定重新实现一下,就可以达到系统升级和扩展的目的!

正好,Java中天生就有interface这个语法,这简直是为面向接口编程而生的!

优点

  • 代码的灵活解耦
  • 代码的扩展性
  • 提高复用
  • 分层开发中,上层不用管具体实现,大家遵守共同的标准,使得开发变得容易,规范性更好

mybatis中的注解开发

mybatis中不推荐使用注解,其他框架推荐

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

用来替代mapper.xml配置文件

  1. mybatis.config.xml中绑定接口
    <!--需要绑定接口,注解替代了mapper配置文件,不再绑定配置文件,需要绑定接口-->
<mappers>
    <mapper class="com.tian.dao.UserMapper"/>
</mappers>
  1. UserMapper.interface
public interface UserMapper {
    //注解
   @Select("select id,name,pwd as password from mybatis.user")
   List<User> getUsers();
}
  1. test.java, 没有变化
public class UserDaoTest {
   @Test
   public void test() {
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      List<User> users = mapper.getUsers();
      for (User user : users) {
         System.out.println(user);
      }
      sqlSession.close();
   }
}

本质

反射机制实现

底层

动态代理

8、Mybatis执行流程

image-20210807224915524

9、多对一、一对多

关联:多个学生,关联一个老师,多对一

集合:一个老师,有很多学生,一对多

。。。

10、动态SQL

理解

Mybatis的强大特性之一就是动态sql,使用动态sql可以拜托不同条件下拼接sql语句的痛苦

动态sql就是根据不同的条件,生成不同的sql语句

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

搭建环境

创建sql表

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

创建一个基础工程

  1. 导包

  2. 编写配置文件mybatis-config.xml和db.properties

<?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="db.properties">
    </properties>
    <settings>
        <!--开启将数据库字段转换成驼峰命名,如create_time变为createTime-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>    
    <!--别名-->
    <typeAliases>
        <package name="com.tian.pojo"/>
    </typeAliases>
    <environments default="development">
        <!--通过id选择默认的事务管理器,默认jdbc,还有一个是MANAGED-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置数据源,UNPOOLED|POOLED|JNDI,默认采用的数据源,POOLED-->
            <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 class="com.tian.dao.BlogMapper"/>
    </mappers>
</configuration>

mybatisUtils类省略

编写IDUtils类,用于生成随机id

import java.util.UUID;
public class IDUtils {
   public static String getId() {
      return UUID.randomUUID().toString().replaceAll("-", "");
   }
}
  1. 编写实体类
@Data
public class Blog {
	private String id;
	private String title;
	private String author;
	//data的属性名和字段名不一致,字段名为create_time
	private Date createTime;
	private int views;
}
  1. 编写实体类对应Mapper接口和Mapper.xml文件
public interface BlogMapper {
   // 插入数据
   int addBlog(Blog blog);
}
<mapper namespace="com.tian.dao.BlogMapper">
    <insert id="addBlog" parameterType="blog">
        insert into mybatis.blog(id, title, author, create_time, views)
        values (#{id},#{title},#{author},#{createTime},#{views});
    </insert>
</mapper>

生成的数据库如下:

image-20210808003003987

动态sql-IF

  1. BlogMapper.interface
	//查询博客
	List<Blog> queryBlogIF(Map map);
  1. BlogMapper.xml
    <select id="queryBlogIF" parameterType="map" resultType="blog">
        <!--作用:当查询语句参数中没有title和author时,输出全部的,有titile或者author时根据
        传入的值进行过滤-->
        select * from mybatis.blog where 1=1
        <if test="title!=null">
            and title = #{title}
        </if>
        <if test="author!=null">
            and author = #{author}
        </if>
    </select>

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

select * from mybatis.blog
<where>
    <if test="title!=null">
        and title = #{title}
    </if>
    <if test="author!=null">
        and author = #{author}
    </if>
</where>
  1. test

传入空值,查询全部

@Test
public void queryBlogIFTest() {
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
   HashMap map = new HashMap();
       //传入空值,查询全部
   List<Blog> blogs = mapper.queryBlogIF(map);
   for (Blog blog : blogs) {
      System.out.println(blog);
   }
   sqlSession.close();
}

传入值,过滤查询

public void queryBlogIFTest() {
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

   HashMap map = new HashMap();
   //赋值,过滤
   map.put("title", "blog1");
   List<Blog> blogs = mapper.queryBlogIF(map);
   for (Blog blog : blogs) {
      System.out.println(blog);
   }
   sqlSession.close();
}

动态sql-choose(when,otherwise)

从多个条件中选择一个使用,有点像 Java 中的 switch 语句

传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就根据views进行查找

 	<select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatis.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>

set元素

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

sql片段

抽取sql部分片段,方便重复代码复用

<!--1. 抽取sql片段-->
<sql id = "if-title-author">
    <if test="title!=null">
        and title = #{title}
    </if>
    <if test="author!=null">
        and author = #{author}
    </if>
</sql>

<select id="queryBlogIF" parameterType="map" resultType="blog">
	select * from mybatis.blog
	<where>
        <!--2. 引入sql片段-->
    	<include refid = "if-title-author"></include>
	</where>

动态sql-Foreach

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

<select id="selectBlogIn" parameterType="map" resultType="blog">
  SELECT *
  FROM mybatis.blog
  WHERE ID in
  <where>
     <foreach item="id" collection="ids"
     	 open="and (" separator="or" close=")">
         id = #{item}
  	</foreach>
  </where>
</select>

11、缓存

简介

什么是缓存

放在内存中的临时数据

一次查询的结果暂时放在内存中,再次查询相同数据的时候直接从缓存取,不用再走数据库了,从而提高查询雄安率,解决了高并发系统的性能问题

为什么使用缓存

减少和数据库的交互次数,减小系统开销,提高系统效率

什么样的数据能使用缓存

经常查询并且不经常改变的数据

Mybatis缓存

mybatis缓存介绍

mybatis默认定义了两级缓存:一级缓存和二级缓存

  • 默认下,只有一级缓存开启,(sqlsession级别的缓存,也叫本地缓存,在sqlsession创建和关闭之间的部分)
  • 二级需要手动开启和配置,基于namespace级别的缓存
  • 为了提高扩展性,mybatis定义了缓存接口cache,可通过实现cache接口自定义二级缓存

一级缓存

默认开启,本地缓存

sqlsession级别的缓存,在sqlsession创建和关闭之间,使用代码查询统一数据多次,只会和数据库交互一次

二级缓存

  • 全局缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,数据被放在当前会话的一级缓存中
    • 会话关闭后,对应的一级缓存就没了,但是我们想要的是会话关闭了,一级缓存中的数据保存到二级缓存中
    • 新的会话查询数据,可以从二级缓存中获取内容
    • 不同的mapper查出的数据会放在自己对应的缓存中

步骤

  1. 开启全局缓存

mybatis-config.xml

    <settings>
        <!--开启全局缓存-->
        <setting name="cacheEnable" value="true"/>
    </settings>
  1. 在mapper.xml中加入<cache/>标签

使用:在mapper.xml中加标签

<cache/>

高级配置:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

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

Mybatis缓存原理