--- title: Mybatis author: TianZD top: true cover: true toc: true mathjax: false summary: Mybatis框架学习笔记,粗略学了一下,没有参考价值 tags: - Mybatis - Java - 数据库 - 学习笔记 categories: - java reprintPolicy: cc_by abbrlink: da3e1361 date: 2022-04-29 11:05:46 coverImg: img: password: --- [toc] # Mybatis # 1、简介 > **Mybatis** * MyBatis 是一款优秀的**持久层**框架 * 它支持自定义 SQL、存储过程以及高级映射 * **MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作** * MyBatis 可以通过简单的 **XML 或注解**来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 获取mybatis ```xml org.mybatis mybatis x.x.x ``` > **持久化** 数据持久化 * 持久化就是将程序的数据在持久状态和瞬时状态转化的过程 * 内存:断电即失 * 数据库、io文件可以进行持久化 为什么需要持久化:有一些对象不能丢失 > **持久层** Dao层、Service层、Controller层 # 2、入门 ## 第一个Mybatis程序 **思路:**搭建环境==》导入Mybatis==》编写代码==》测试 > **搭建环境** 1. **搭建数据库** ```sql 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; ``` 2. **新建maven项目** 1. 普通maven项目 2. 删除src目录,使其变为父工程 3. 导入依赖 ```xml mysql mysql-connector-java 8.0.25 org.mybatis mybatis 3.5.7 junit junit 3.8.2 test ``` > **创建模块** 由于在上述步骤中配置了父工程,并且父工程已经导入了依赖,因此所有的子模块不需要再次引入依赖 子模块的xml配置文件中多了<**parent**> ```xml MybatisStudy org.example 1.0-SNAPSHOT ``` > **编写mybatis核心配置文件,在resource目录下创建mybaits-config.xml** XML 配置文件中包含了**对 MyBatis 系统的核心设置**,包括**获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器**(TransactionManager)。 ```xml ``` > **编写mybatis工具类** * **从xml配置中获取 SqlSessionFactory 实例** * **从 SqlSessionFactory 中获取 SqlSession** 每个基于 MyBatis 的应用都是以一个 **SqlSessionFactory 的实例为核心**的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则**可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例**。 从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。 **SqlSession中包括了操作数据库的方法** ```java 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(); } } ``` > **编写代码** * **实体类** ```java 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接口** ```java 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 getUserList(); //根据id查询 User getUserById(int id); //insert插入 int addUser(User user); //update修改 int updateUser(User user); //删除用户 int deleteUser(int id); } ``` * **接口实现类Mapper** 以前采用的方式是创建一个**接口实现类,现在采用xml**配置文件的方式 ```xml insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd}); update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}; delete from mybatis.user where id = #{id}; ``` * **Junit测试** ```java 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 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. 在测试方法中增加相应的测试方法 **注意**:增删改需要在关闭之前提交事务 ```java sqlSession.commit(); ``` > **可能错误:** * org.apache.ibatis.binding.BindingException: Type interface com.tian.dao.UserDao is not known to the MapperRegistry. 在mybatis.config.xml文件中没有配置mapper.xml 增加如下: **注意,路径用斜杠隔开** ```xml ``` * 错误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,防治资源导出失败 ```xml src/main/resources **/*.properties **/*.xml false src/main/java **/*.properties **/*.xml false ``` ## 生命周期和作用域 作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的**并发问题** ![image-20210807175425111](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807175425111.png) > **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 关闭的标准模式: ```java try (SqlSession session = sqlSessionFactory.openSession()) { // 你的应用逻辑代码 } ``` ![image-20210807175744673](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807175744673.png) 每一个Mapper代表一个具体的业务 ## CRUD > **namespace** namespace中的包名要和Dao/Mapper接口的包名保持一致 ```xml ``` 绑定时的包名用.不能用/ > **select** 选择、查询语句 1. 编写接口 ```java //查询全部 List getUserList(); //根据id查询 User getUserById(int id); ``` 2. 编写对应的mapper中的sql语句 ```xml ``` 3. 编写测试 ```java @Test public void test(){ //1. 获得SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2. 执行sql、获取结果、输出 //方式1,getMapper UserDao userDao = sqlSession.getMapper(UserDao.class); List 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语句 ```xml insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd}); ``` 3. 测试:增删改需要提交事务 ```java // 增删改需要提交事务 sqlSession.commit(); ``` > **update** 1. 编写接口 2. sql语句 ```xml update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}; ``` 3. 测试:需要提交事务 > **Delete** 1. 接口 2. sql语句 ```xml delete from mybatis.user where id = #{id}; ``` 3. 测试:需要提交事务 ## 使用Map传参 假如我们的实体类或者数据库中的表、字段或者参数过多,我们应当考虑使用Map 使用User对象时,假如需要修改密码,当字段过多时,sql语句中还需要把其他字段给加上,很麻烦 使用map时: 1. 接口 ```java int updateUser2(Map map); ``` 2. sql语句 sql语句中传入的参数类型为map 具体传入的参数不需要和数据库以及实体类中的对应,在map.put()中进行对应即可 ```xml update mybatis.user set pwd = #{userPwd} where id = #{userId}; ``` 3. 测试 ```java @Test public void updateUser2Test() { // 1.获取sqlsession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); // 2.sql UserDao mapper = sqlSession.getMapper(UserDao.class); // map Map map = new HashMap<>(); map.put("userId", 3); map.put("userPwd", "000000"); int i = mapper.updateUser2(map); // 增删改需要提交事务 sqlSession.commit(); // 3.关闭 sqlSession.close(); } ``` ## 模糊查询 模糊查询需要防止sql注入 1. java代码执行的时候,传入通配符% % ```java List u = mapper.getUserLike("%tian%"); ``` 2. mapper中的sql语句使用where 字段 like 加%,会导致sql注入 ```xml 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 ```xml ``` **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: ```properties driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username=root password=123456 ``` 2. xml中引入配置文件 在xml中,所有的标签都可以规定其顺序,properties=》settings=》typerAliases。。properties只能放在最上面 * 中间可以设置属性,设置用户名和密码 * 如果两个设置的属性中和db.properties中有同一个字段,优先使用外部的db.properties ```xml ``` 3. 在整个配置文件中用来替换需要动态配置的属性值 ```xml ``` ## 类型别名typerAliases 类型别名可为 Java 类型设置一个**缩写名字**。 它仅用于 XML 配置,意在**降低冗余的全限定类名**书写。 ```xml ``` 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如: 在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 `domain.blog.Author` 的别名为 `author` ```xml ``` 使用`com.tian.pojo.User`时只需要用`user`即可 **在实体类比较少的时候,使用第一种,实体类多的时候使用第二种,第一种可以自定义别名,第二种可以通过在实体类上增加注解来自定义别名,如下:** ```java @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 元素的示例如下: ```xml ``` ## 其他配置 * 类型处理器typeHandlers * 对象工厂objectFactory * 插件plugins * mybatis-generator-core * mybatis-plus:一个增强工具,简化mybatis * 通用mapper * 数据库厂商标识databaseProvider ## 映射器mappers MapperRegistry:注册绑定我们的Mapper文件 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们**需要告诉 MyBatis 到哪里去找到这些语句**。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 `file:///` 形式的 URL),或类名和包名等。 **maven中resources文件夹下的文件在编译后,都放在了根目录下** > **方式1 :推荐使用** ```xml ``` > **方式2:使用class文件** **注意**: * 接口和他的Mapper配置文件必须同名 * 接口和他的Mapper配置文件必须在同一个包下(Mapper配置文件可以在maven的resources目录下) ```xml ``` > **方式3**:使用扫描包进行绑定注入 **注意:** * 接口和他的Mapper配置文件必须同名 * 接口和他的Mapper配置文件必须在同一个包下 ```xml ``` # 4、解决属性名和字段名不一致 > **问题** 如:数据库中的字段名为pwd,实体类中的属性名为password User: ```java public class User { private int id; private String name; private String password; ... } ``` Mapper: ```xml ``` test: ```java @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语句: ```xml ``` * resultMap ## resultMap > 结果集映射 * `resultMap`元素是Mybatis中最重要最强大的元素 * 设计思想是:对于简单的语句根本不需要配置显式的结果集映射,对于复杂的语句,只需要描述他们的关系就可以了 > 简单的结果集映射 ```xml ``` > 复杂 一对多,多对一 # 5、日志 ## 日志工厂 > **介绍** 如果一个数据库操作出现了异常,需要排错,需要日志 曾经用:sout、debug 现在:日志工厂 mybatis提供的: * SLF4J * **LOG4J** * LOG4J2 * JDK_LOGGING * COMMONS_LOGGING * **STDOUT_LOGGING** * NO_LOGGING 具体使用哪一个,在mybatis-config.xml中的settings中设置,默认不使用 ![image-20210807195224236](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807195224236.png) > **标准的日志工厂实现** 使用`STDOUT_LOGGING`不需要导包 * 在mybatis-config.xml中配置日志 ```xml ``` * 配置后输出: ```bash 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. 导包 ```xml log4j log4j 1.2.17 ``` 2. 新增配置文件`log4j.properties` 网上找即可 ```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 ``` 3. 配置log4j为日志的实现 ```XML ``` 4. **log4j使用** * 在要使用log4j的类中,导入包`import org.apache.log4j.Logger` * 获取日志对象,参数为当前类的class ```java static Logger logger = Logger.getLogger(UserDaoTest.class); ``` * 编写测试代码 日志级别:info\debug\error ```java 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方法"); } } ``` * 输出 在控制台和配置的输出文件中会输出以下: ```bash [com.tian.dao.UserDaoTest]-info:进入了testLog4j方法 [com.tian.dao.UserDaoTest]-debug:进入了testLog4j方法 [com.tian.dao.UserDaoTest]-error:进入了testLog4j方法 ``` # 6、分页 > **为什么要分页** 减少数据的处理量 > **limit分页** ```sql SELECT * FROM user LIMIT startIndex,pageSize; ``` > 使用mybatis分页 核心sql 1. 接口 ```java // 分页查询 List getUserByLimit(Map map); ``` 2. Mapper.xml ```xml ``` 3. 测试 ```java 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 map = new HashMap<>(); map.put("startIndex", 1); map.put("pageSize", 2); //2.2 sql List 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**中绑定接口 ```xml ``` 2. **UserMapper.interface** ```java public interface UserMapper { //注解 @Select("select id,name,pwd as password from mybatis.user") List getUsers(); } ``` 3. **test.java, 没有变化** ```java public class UserDaoTest { @Test public void test() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); } } ``` > **本质** **反射机制实现** > **底层** **动态代理** # 8、Mybatis执行流程 ![image-20210807224915524](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807224915524.png) # 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表** ```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 ``` **mybatisUtils类省略** **编写IDUtils类,用于生成随机id** ```java import java.util.UUID; public class IDUtils { public static String getId() { return UUID.randomUUID().toString().replaceAll("-", ""); } } ``` 3. 编写实体类 ```java @Data public class Blog { private String id; private String title; private String author; //data的属性名和字段名不一致,字段名为create_time private Date createTime; private int views; } ``` 4. 编写实体类对应Mapper接口和Mapper.xml文件 ```java public interface BlogMapper { // 插入数据 int addBlog(Blog blog); } ``` ```xml insert into mybatis.blog(id, title, author, create_time, views) values (#{id},#{title},#{author},#{createTime},#{views}); ``` **生成的数据库如下:** ![image-20210808003003987](https://gitee.com/tianzhendong/img/raw/master//images/image-20210808003003987.png) ## 动态sql-IF 1. **BlogMapper.interface** ```java //查询博客 List queryBlogIF(Map map); ``` 2. **BlogMapper.xml** ```xml ``` where属性:*where* 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,*where* 元素也会将它们去除。 ```xml select * from mybatis.blog and title = #{title} and author = #{author} ``` 3. **test** **传入空值,查询全部** ```java @Test public void queryBlogIFTest() { SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); //传入空值,查询全部 List blogs = mapper.queryBlogIF(map); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); } ``` **传入值,过滤查询** ```java public void queryBlogIFTest() { SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); //赋值,过滤 map.put("title", "blog1"); List 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进行查找 ```xml ``` ## set元素 用于动态更新语句的类似解决方案叫做 *set*。*set* 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如 ```xml update Author username=#{username}, password=#{password}, email=#{email}, bio=#{bio} where id=#{id} ``` *set* 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。 ## sql片段 抽取sql部分片段,方便重复代码复用 ```xml and title = #{title} and author = #{author} SELECT * FROM mybatis.blog WHERE ID in id = #{item} ``` # 11、缓存 ## 简介 > **什么是缓存** 放在内存中的临时数据 一次查询的结果暂时放在内存中,再次查询相同数据的时候直接从缓存取,不用再走数据库了,从而提高查询雄安率,解决了高并发系统的性能问题 > **为什么使用缓存** 减少和数据库的交互次数,减小系统开销,提高系统效率 > **什么样的数据能使用缓存** 经常查询并且不经常改变的数据 ## Mybatis缓存 > mybatis缓存介绍 mybatis默认定义了两级缓存:一级缓存和二级缓存 * 默认下,只有一级缓存开启,(sqlsession级别的缓存,也叫本地缓存,在sqlsession创建和关闭之间的部分) * 二级需要手动开启和配置,基于namespace级别的缓存 * 为了提高扩展性,mybatis定义了缓存接口cache,可通过实现cache接口自定义二级缓存 > **一级缓存** 默认开启,本地缓存 sqlsession级别的缓存,在sqlsession创建和关闭之间,使用代码查询统一数据多次,只会和数据库交互一次 > **二级缓存** * 全局缓存 * 基于namespace级别的缓存,一个名称空间,对应一个二级缓存 * 工作机制 * 一个会话查询一条数据,数据被放在当前会话的一级缓存中 * 会话关闭后,对应的一级缓存就没了,但是我们想要的是会话关闭了,一级缓存中的数据保存到二级缓存中 * 新的会话查询数据,可以从二级缓存中获取内容 * 不同的mapper查出的数据会放在自己对应的缓存中 **步骤**: 1. 开启全局缓存 mybatis-config.xml ```xml ``` 2. 在mapper.xml中加入``标签 使用:在mapper.xml中加标签 ```xml ``` 高级配置: ```xml ``` 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。 ## Mybatis缓存原理 ![](https://gitee.com/tianzhendong/img/raw/master//images/image-20210808041200380.png)