当前位置: 代码迷 >> 综合 >> 从零入门MyBatis完整学习笔记(包含MyBatis各类基本配置,CRUD,结果集映射,分页,注解开发,动态SQL以及MyBatis缓存)
  详细解决方案

从零入门MyBatis完整学习笔记(包含MyBatis各类基本配置,CRUD,结果集映射,分页,注解开发,动态SQL以及MyBatis缓存)

热度:6   发布时间:2023-12-17 09:37:34.0

本篇学习笔记总结自bilibiliup主【狂神说Java】系列视频:【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂
视频作者公众号:狂神说

1、简介

1.1、什么是MyBatis

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

1.2、如何获得MyBatis?

  • Maven仓库

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.2</version>
    </dependency>
    
  • Github:https://github.com/mybatis/mybatis-3/releases

  • 中文文档:https://mybatis.org/mybatis-3/zh/index.html

1.3、持久化

  • 数据持久化:将程序的数据在持久状态和瞬时状态转化的过程

  • 内存:断电即失

  • 数据库(JDBC),IO文件持久化

1.4、持久层

Dao层,Service层,Controller层

  • 完成持久化工具的代码块
  • 层是界限十分明显的
  • 优点
    • 简单易学,零花
    • SQL与代码分离,提高可维护性
    • 提供映射标签,支持对象与数据库的ORM字段关系映射
    • 提供对象映射标签,支持对象关系组件维护
    • 提供xml标签,支持编写动态SQL

2、第一个MyBatis程序

2.1、搭建环境

  1. MySQL数据库:
create table `user`(`user_id` int(20) not null primary key ,`user_name` varchar(30) not null ,`user_pwd` varchar(30) not null
)engine = innodb default character set = utf8;insert into `user`(`user_id`,`user_name`,`user_pwd`) values (1,'neil','123123'),(2,'张三','123123'),(3,'李四','123123'),(4,'王五','123123')
  1. 创建一个普通maven项目
  2. 删除src目录
  3. 导入maven依赖

2.2、工具类以及配置文件

  • 编写mybatis核心配置文件

  • 编写mybatis工具类mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configurationPUBLIC "-//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.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="com/neil/dao/UserMapper.xml"/></mappers>
    </configuration>
    
  • 编写myBatis工具类

    //sqlSessionFactory
    public class MybatisUtils {
          private static SqlSessionFactory sqlSessionFactory;static{
          String resource = "org/mybatis/example/mybatis-config.xml";InputStream inputStream = null;{
          try {
          inputStream = Resources.getResourceAsStream(resource);} catch (IOException e) {
          e.printStackTrace();}}sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}public static SqlSession getSqlSession(){
          //这里传入true表示事务的autoCommit打开return sqlSessionFactory.openSession(true);}
    }
    
  • UserDao

    public interface UserDao {List<User> getUserList();
    }
    
  • 接口实现类从原来的UserDaoImpl转变为一个Mapper配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace:绑定一个对应的Dao(Mapper)接口-->
    <mapper namespace="com.neil.dao.UserDao"><!--id对应对应Mapper接口的方法名字--><select id="getUserList" resultType="com.neil.pojo.User">select * from mybatis.user</select>
    </mapper>
    

2.4、测试

注意点:

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

MapperRegistry:

需要在mybatis-config.xml中配置

<mappers><mapper resource="com/neil/dao/UserMapper.xml"/>
</mappers>

测试最好使用try包裹起来

public class UserDaoTest {
    @Testpublic void test(){
    //获得sqlSession对象,在tyr()里面的会自动释放try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
    //方式一:getMappter执行SQLUserDao mapper = sqlSession.getMapper(UserDao.class);List<User> userList = mapper.getUserList();for (User user : userList) {
    System.out.println(user.getUser_name());}}}
}

3、CRUD

3.1、namespace

namespace中的包名需要和mapper的接口名字一样

3.2、案例

  1. 编写接口

    public interface UserMapper {
          //查询全部用户List<User> getUserList();//根据ID查询用户User getUserById(int id);//insert一个用户int addUser(User user);//修改用户int updateUser(User user);//删除int deleteUser(int id);
    }
    
  2. 编写mapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace:绑定一个对应的Dao(Mapper)接口-->
    <mapper namespace="com.neil.dao.UserMapper"><!--id对应对应Mapper接口的方法名字--><select id="getUserList" resultType="com.neil.pojo.User">select * from mybatis.user</select><!--id对应对应Mapper接口的方法名字 mybatis.user表示在mybatis这个数据库里面的user表--><select id="getUserById" parameterType="int" resultType="com.neil.pojo.User" >select * from mybatis.user where user_id = #{id}</select><!--对象中的属性可以直接取出来--><insert id="addUser" parameterType="com.neil.pojo.User">insert into mybatis.user(user_id,user_name,user_pwd) values(#{user_id},#{user_name},#{user_pwd});</insert><update id="updateUser" parameterType="com.neil.pojo.User">update mybatis.user set user_name=#{user_name},user_pwd=#{user_pwd} where user_id = #{user_id}</update><delete id="deleteUser" parameterType="int">delete from mybatis.user where user_id = #{id};</delete>
    </mapper>
    
  3. 测试(增删改需要提交事务)

选择,查询语句:

  • id:就是对应的namespace中的方法名字:
  • resultType:sql语句执行的返回值

3.3、Map

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

直接在sql中取出key即可,对象成为参数,需要直接从sql中取对象的属性

  1. 接口:int addUserByMap(Map<String,Object> map);

  2. mapper

    <!--使用map-->
    <insert id="addUserByMap" parameterType="map">insert into mybatis.user(user_id,user_name,user_pwd) values (#{userid},#{username},#{userpwd})
    </insert>
    
  3. Test

    @Test
    public void addUserMapTest(){
          try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
          //方式一:getMappter执行SQLUserMapper mapper = sqlSession.getMapper(UserMapper.class);Map<String, Object> map = new HashMap<String, Object>();//使用map不用new对象map.put("userid",5);map.put("userpwd","12313");map.put("username","UserMap");int success = mapper.addUserByMap(map);if(success>0){
          System.out.println("增加成功");}//提交事务sqlSession.commit();//提交事务}
    }
    

3.4、模糊查询

两种方式,需要注意SQL注入问题

  1. Java代码执行的时候,传递通配符%%

    List<User> userList = mapper.getUserLike("%李%");
    
  2. 在SQL拼接的时候使用通配符

    select * from user where name like ""#{
          value}""
    

4、配置解析

4.1、核心配置文件

  • mybatis-config.xml

    MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

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

4.2、环境变量 (environment)

https://mybatis.org/mybatis-3/zh/configuration.html#environments

MyBatis可以配置多种运行环境,但是每一个sqlSessionFactory实例只能选择一套环境

MyBatis默认事务管理器是JDBC(可选managed),连接池:pooled(可选unpooled)

4.3、属性 (Properties)

我们可以用属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置【db.properties】

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456

mybatis-config.xml中引用

<!--引入外部配置文件-->
<properties resource="db.properties"><property name="username" value="root"/><property name="pwd" value="123456"/>
</properties>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性
  • 两个文件有同样的key,优先使用mybatis-config.xml的配置

4.4、类型别名(typeAliases)

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

<typeAliases><typeAlias alias="Author" type="domain.blog.Author"/><typeAlias alias="Blog" type="domain.blog.Blog"/><typeAlias alias="Comment" type="domain.blog.Comment"/><typeAlias alias="Post" type="domain.blog.Post"/><typeAlias alias="Section" type="domain.blog.Section"/><typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

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

<typeAliases><package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

4.5、设置(settings)

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false

4.6、其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    • mybatis-plus. mybatis增强工具
    • Mybatis-generator-core
    • 通用mapper

4.7、映射器(mappers)

**MapperRegistry:**在mybatis-config.xml注册绑定mapper

<!-- 使用相对于类路径的资源引用 推荐 -->
<mappers><mapper resource="org/mybatis/builder/AuthorMapper.xml"/><mapper resource="org/mybatis/builder/BlogMapper.xml"/><mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) 不推荐使用-->
<mappers><mapper url="file:///var/mappers/AuthorMapper.xml"/><mapper url="file:///var/mappers/BlogMapper.xml"/><mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名-->
<!-- 路径需要和接口Mapper路径一样,不然可能找不到-->
<!-- 必须UserMapper.java 名字对应 UserMapper.xml-->
<mappers><mapper class="org.mybatis.builder.AuthorMapper"/><mapper class="org.mybatis.builder.BlogMapper"/><mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<!-- 路径需要和接口Mapper路径一样,不然可能找不到-->
<!-- 必须UserMapper.java 名字对应 UserMapper.xml-->
<mappers><package name="org.mybatis.builder"/>
</mappers>

注:也可以尝试在resources下创建相同路径的文件夹,把xml文件放进去实现分离,不过需要一个文件夹一个文件夹创建

4.8、作用域(Scope)和生命周期

生命周期和作用域至关重要,错误的使用可能会导致非常严重的并发问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHl3fEaF-1588921857557)(MyBatis.assets/截屏2020-04-26 下午5.43.01.png)]

SqlSessionFactoryBuilder

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

  • 一旦创建SqlSessionFactoryBuilder,就不再需要
  • 局部变量

SqlSessionFactory

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

  • 可以想象成数据库连接池
  • 使用单例或者静态单例模式

SqlSession

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

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

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

  • 链接到连接池的一个请求

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域

  • 请求结束需要关闭

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ofusbURy-1588921857559)(MyBatis.assets/截屏2020-04-26 下午5.49.17.png)]

这里面每一个Mapper就代表一个具体的业务

5、解决属性名和字段名不一致的问题

新建一个项目,拷贝之前的,测试实体类字段不一样

package com.neil.pojo;//实体类
public class User {
    private int id;private String pwd;private String name;public User(int id, String pwd, String name) {
    this.id = id;this.pwd = pwd;this.name = name;}@Overridepublic String toString() {
    return "User{" +"id=" + id +", pwd='" + pwd + '\'' +", name='" + name + '\'' +'}';}public int getId() {
    return id;}public void setId(int id) {
    this.id = id;}public String getPwd() {
    return pwd;}public void setPwd(String pwd) {
    this.pwd = pwd;}public String getName() {
    return name;}public void setName(String name) {
    this.name = name;}public User() {
    }
}

此时私有属性与数据库字段不一样,有几种解决方式:

  1. 数据库查询取别名,使其更改为私有属性的名称一致

    <!--id对应对应Mapper接口的方法名字 mybatis.user表示在mybatis这个数据库里面的user表-->
    <select id="getUserById" parameterType="int" resultType="user" >select user_id as id,user_name as name,user_pwd as pwd from mybatis.user where user_id = #{id}
    </select>
    
  2. resultMap

    <mapper namespace="com.neil.dao.UserMapper"><!--结果集映射到user--><resultMap id="UserMap" type="user"><!--column表示字段,property表示实体类属性--><result column="user_id" property="id"/><result column="user_name" property="name"/><result column="user_pwd" property="pwd"/></resultMap><!--id对应对应Mapper接口的方法名字 mybatis.user表示在mybatis这个数据库里面的user表--><select id="getUserById" parameterType="int" resultType="user" resultMap="UserMap" >select user_id,user_name,user_pwd from mybatis.user where user_id = #{id}</select>
    </mapper>
    
    • resultMap元素是MyBatis中最重要最强大的元素
    • resultMap的设计思想是,对于简单的语句根本不需要配置显示进行映射结果集,而对于复杂一点的语句只需要描述他们的关系就可以了
    • resultMap最优秀的地方在于,有时候不需要直接定义

6、日志工厂

6.1、基本配置

在设置中有如下设置来设置日志工厂

设置名 描述 有效值(主要) 默认值
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 Log4J | Log4j2| SLF4J | JDK_LOGGING |COMMONS_LOGGING| STDOUT_LOGGING| No_logging

STDOUT_LOGGING为标准日志输出

mybatis-config.xml中配置

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

6.2、使用Log4j

  1. 导入jar包

    <dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
    </dependency>
    
  2. 配置文件编写

    在resources建立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/Neil.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. mybatis-config.xml中配置

    <settings><setting name="logImpl" value="LOG4J"/>
    </settings>
    
  4. 在程序中使用Log4j进行输出!

    //注意导包:org.apache.log4j.Logger
    static Logger logger = Logger.getLogger(MyTest.class);@Test
    public void selectUser() {
          logger.info("info:进入selectUser方法");logger.debug("debug:进入selectUser方法");logger.error("error: 进入selectUser方法");SqlSession session = MybatisUtils.getSession();UserMapper mapper = session.getMapper(UserMapper.class);List<User> users = mapper.selectUser();for (User user: users){
          System.out.println(user);}session.close();
    }
    

7、分页

7.1、Limit实现

思考:为什么需要分页?

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

核心思想:将需要分页的信息通过Map的方式传入UserMapper.xml,之后拼接字符串

SELECT * FROM table LIMIT stratIndex,pageSizeSELECT * FROM table LIMIT 5,10; // 检索记录行 6-15  

步骤:

  1. mapper文件

    <select id="selectUser" parameterType="map" resultType="user">select * from user limit #{startIndex},#{pageSize}
    </select>
    
  2. Mapper接口,参数为map

    //选择全部用户实现分页
    List<User> selectUser(Map<String,Integer> map);
    
  3. 在测试类中传入参数测试

    //分页查询 , 两个参数startIndex , pageSize
    @Test
    public void testSelectUser() {
          SqlSession session = MybatisUtils.getSession();UserMapper mapper = session.getMapper(UserMapper.class);int currentPage = 1;  //第几页int pageSize = 2;  //每页显示几个Map<String,Integer> map = new HashMap<String,Integer>();map.put("startIndex",(currentPage-1)*pageSize);map.put("pageSize",pageSize);List<User> users = mapper.selectUser(map);for (User user: users){
          System.out.println(user);}session.close();
    }
    

7.2、RowBounds分页

步骤:

  1. mapper接口

    //选择全部用户RowBounds实现分页
    List<User> getUserByRowBounds();
    
  2. mapper文件

    <select id="getUserByRowBounds" resultType="user">select * from user
    </select>
    
  3. 测试类

    @Test
    public void testUserByRowBounds() {
          SqlSession session = MybatisUtils.getSession();int currentPage = 2;  //第几页int pageSize = 2;  //每页显示几个RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);//通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);for (User user: users){
          System.out.println(user);}session.close();
    }
    

7.3、分页插件(pageHelper)

官方文档:https://pagehelper.github.io/

8、使用注解开发

8.1、面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

8.2、注解

  • mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建

  • sql 类型主要分成 :

    • @select ()
    • @update ()
    • @Insert ()
    • @delete ()
  • **注意:**利用注解开发就不需要mapper.xml映射文件了 .

步骤:

  1. 在接口中添加注解

    //查询全部用户
    @Select("select id,name,pwd password from user")
    public List<User> getAllUser();
    
  2. 在mybatis的核心配置 mybatis-config.xml文件中注入

    <!--使用class绑定接口-->
    <mappers><mapper class="com.kuang.mapper.UserMapper"/>
    </mappers>
    
  3. 测试

    @Test
    public void testGetAllUser() {
          SqlSession session = MybatisUtils.getSession();//本质上利用了jvm的动态代理机制UserMapper mapper = session.getMapper(UserMapper.class);List<User> users = mapper.getAllUser();for (User user : users){
          System.out.println(user);}session.close();
    }
    

使用注解开发本质上是实现了jvm的动态代理机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mlavtY22-1588921857560)(MyBatis.assets/640.png)]

8.3、Mybatis详细的执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2fgDhsTO-1588921857562)(MyBatis.assets/640-20200427121138344.png)]

8.4、CRUD

改造MybatisUtils工具类的getSession( ) 方法,重载实现。

  //获取SqlSession连接
public static SqlSession getSession(){
    return getSession(true); //事务自动提交
}public static SqlSession getSession(boolean flag){
    return sqlSessionFactory.openSession(flag);
}

【注意】确保实体类和数据库字段对应

查询:

  1. 编写接口方法注解

    //根据id查询用户
    @Select("select * from user where id = #{ParamId}")
    User selectUserById(@Param("ParamId") int id);
    
  2. 测试

    @Test
    public void testSelectUserById() {
          SqlSession session = MybatisUtils.getSession();UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.selectUserById(1);System.out.println(user);session.close();
    }
    

新增:

  1. 编写接口方法注解

    //添加一个用户,引用对象不能使用@Param
    @Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
    int addUser(User user);
    
  2. 测试

    @Test
    public void testAddUser() {
          SqlSession session = MybatisUtils.getSession();UserMapper mapper = session.getMapper(UserMapper.class);User user = new User(6, "Neil", "123456");mapper.addUser(user);session.close();
    }
    

8.5、@Param

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

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

8.6、#与$的区别

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
    
  • ${} 的作用是直接进行字符串替换 【不安全,可能还会有SQL注入问题】

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('kuangshen');
    

9、Lombok

使用步骤:

  1. 在IDEA插件安装Lombok

  2. 导入maven依赖

    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version>
    </dependency>
    
  3. 案例

    @Data // 自动生成GET,SET,ToString,有参,无参构造
    public class Teacher {
          private int id;private String name;
    }
    
  4. 常用注解

    @Data 注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
    @Setter :注解在属性上;为属性提供 setting 方法
    @Setter :注解在属性上;为属性提供 getting 方法
    @Log4j:注解在类上;为类提供一个 属性名为log 的 log4j 日志对象
    @NoArgsConstructor:注解在类上;为类提供一个无参的构造方法
    @AllArgsConstructor :注解在类上;为类提供一个全参的构造方法
    @Cleanup : 可以关闭流
    @Builder : 被注解的类加个构造者模式
    @Synchronized: 加个同步锁
    @SneakyThrows : 等同于try/catch 捕获异常
    @NonNull : 如果给参数加个这个注解 参数为null会抛出空指针异常
    @Value : 注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法

    @ResultMap:表示在mapper.xml文件中对应的ResultMap的Id值

    来源:https://blog.csdn.net/u011308294/article/details/78627560

10、多对一

10.1、案例数据库设计

CREATE TABLE `teacher` (
`tea_id` INT(10) NOT NULL,
`tea_name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`tea_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO teacher(`tea_id`, `tea_name`) VALUES (1, '秦老师');CREATE TABLE `student` (
`stu_id` INT(10) NOT NULL,
`stu_name` VARCHAR(30) DEFAULT NULL,
`stu_tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`stu_id`),
KEY `fktid` (`stu_tid`),
CONSTRAINT `stu_fktid` FOREIGN KEY (`stu_tid`) REFERENCES `teacher` (`tea_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO `student` (`stu_id`, `stu_name`, `stu_tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`stu_id`, `stu_name`, `stu_tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`stu_id`, `stu_name`, `stu_tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`stu_id`, `stu_name`, `stu_tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`stu_id`, `stu_name`, `stu_tid`) VALUES ('5', '小王', '1');

10.2、测试环境搭建

  1. 导入lombok

  2. 创建实体类Teacher, Student(已忽略getter/setter)

    public class Student {
          private String stuId;private String stuName;private Teacher teacher;
    }
    
    public class Teacher {
          private int teaId;private String teaName;
    }
    
  3. 建立Mapper接口

  4. 建立Mapper.xml文件

  5. 在核心配置文件中绑定注册Mapper接口或文件

  6. 测试查询

在这里插入图片描述

10.3、按照查询嵌套处理

StudentMapper.xml

<mapper namespace="com.neil.dao.StudentMapper"><!--思路:1:查询所有的学生信息 2:对应学生所有的tid,寻找对应的老师--><select id="getStudentsAndTeacher" resultMap="StudentAndTeacherMapping">select * from student</select><select id="getTeacher" resultType="Teacher" resultMap="teacherMapping">select * from teacher where tea_id = #{id}</select><!--结果集映射到user--><resultMap id="StudentAndTeacherMapping" type="Student"><id property="stuId" column="stu_id"/><result property="stuName" column="stu_name"/><!--负责的属性我们需要单独处理--><!--对象使用:association--><!--集合使用:collection--><association property="teacher" column="stu_tid" javaType="Teacher" select="getTeacher"/></resultMap><resultMap id="teacherMapping" type="Teacher"><id property="teaId" column="tea_id"/><result property="teaName" column="tea_name"/></resultMap></mapper>

10.4、按照结果嵌套处理

StudentMapper.xml

<!--按照结果查询处理-->
<select id="getStudentsAndTeacher2" resultMap="StudentAndTeacherMapping2">select stu_name,stu_id,tea_name from student,teacher where stu_tid = tea_id
</select><resultMap id="StudentAndTeacherMapping2" type="Student"><result property="stuId" column="stu_id"/><result property="stuName" column="stu_name"/><association property="teacher" javaType="Teacher"><result property="teaName" column="tea_name"/><result property="teaId" column="tea_id"/></association>
</resultMap>

10.5、结论

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

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

select stu_name,stu_id,tea_name from student,teacher where stu_tid = tea_id

11、一对多

比如,一个老师拥有多个学生

对于老师而言就是一对多的关系

11.1、环境搭建

实体类:

public class Student {
    private String stuId;private String stuName;private int tid;
}
public class Teacher {
    private int teaId;private String teaName;private List<Student> students;
}

11.2、按照结果嵌套处理

TeacherMapper.xml

<!--按结果嵌套查询-->
<select id="getTeacherById" resultType="teacher" resultMap="getTeacherByIdMapping">select tea_id,tea_name,stu_name from teacher, student where tea_id = stu_tid and tea_id=#{tid}
</select><resultMap id="getTeacherByIdMapping" type="teacher"><result column="tea_id" property="teaId"/><result column="tea_name" property="teaName"/><!--复杂的属性:我们需要单独处理 对象:association 集合:collection--><!--javaType:指定的属性类型 集合中的泛型信息,我们需要用ofType获取--><collection property="Students" ofType="student"><result property="stuId" column="stu_id"/><result property="stuName" column="stu_name"/><result property="stuTid" column="stu_id"/></collection>
</resultMap>

11.3、按照查询嵌套处理

TeacherMaper.xml

<select id="getTeacherById2" resultMap="getTeacherById2Mapping">select * from teacher where tea_id = #{tid}
</select><resultMap id="getTeacherById2Mapping" type="teacher"><result column="tea_id" property="teaId"/><result column="tea_name" property="teaName"/><!--这里的column指的是,将老师的字段tea_id放入 后面的#{tid}里面--><collection column="tea_id" property="Students" javaType="ArrayList" ofType="student" select="getTeacherById2StudentMapping" />
</resultMap><select id="getTeacherById2StudentMapping" resultType="student" resultMap="studentMapping">select * from student where stu_tid = #{tid}
</select><resultMap id="studentMapping" type="student"><result property="stuId" column="stu_id"/><result property="stuName" column="stu_name"/><result property="stuTid" column="stu_id"/>
</resultMap>

11.4、小结

1、关联-association

2、集合-collection

3、所以association是用于一对一和多对一,而collection是用于一对多的关系

4、JavaType和ofType都是用来指定对象类型的

  • JavaType是用来指定pojo中属性的类型
  • ofType指定的是映射到list集合属性中pojo的类型。

注意说明:

1、保证SQL的可读性,尽量通俗易懂

2、根据实际要求,尽量编写性能更高的SQL语句

3、注意属性名和字段不一致的问题

4、注意一对多和多对一 中:字段和属性对应的问题

5、尽量使用Log4j,通过日志来查看自己的错误

12、动态SQL

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

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

##12.1、搭建环境

数据库:

create Table `blog`(`blog_id` varchar(50) not null primary key,`blog_title` varchar(50) not null ,`blog_author` varchar(30) not null ,`create_time` datetime not null ,`blog_views` int(30) not null
)engine = InnoDB default charset =utf8

pojo:

public class Blog {
    private String id;private String title;private String author;private int views;private Date createTime;
}

BlogMapper.xml

<mapper namespace="com.neil.dao.BlogMapper"><insert id="addBlog" parameterType="blog">insert into blog(blog_id, blog_title, blog_author, create_time, blog_views) values (#{id},#{title},#{author},#{createTime},#{views})</insert>
</mapper>

测试类:

@Test
public void addBlogTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);Blog blog = new Blog();blog.setAuthor("Neil");blog.setCreateTime(new Date());blog.setId(Idutils.getId());blog.setTitle("SpringCloud");blog.setViews(123);int i = mapper.addBlog(blog);System.out.println(i);
}

12.2、If表达式拼接

接口:

//查询博客
List<Blog> queryBlogIf(Map map);

BlogMapper.xml

<select id="queryBlogIf" parameterType="map" resultType="Blog" resultMap="BlogMapping">select * from blog where 1=1<if test="title != null">and blog_title = #{title}</if><if test="author != null">and blog_author = #{author}</if>
</select><resultMap id="BlogMapping" type="Blog"><id javaType="String" column="blog_id" property="id"/><result property="title" column="blog_title"/><result property="author" column="blog_author"/><result property="views" column="blog_views"/><result property="createTime" column="create_time"/>
</resultMap>

测试类:

@Test
public void queryBlog(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);HashMap<String, String> map = new HashMap<>();//map.put("title","三体");map.put("author","Neil");List<Blog> blogs = mapper.queryBlogIf(map);for (Blog blog : blogs) {
    System.out.println(blog.toString());}
}

12.3、choose、when、otherwise、where

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

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

<select id="queryBlogIf" parameterType="map" resultType="Blog" resultMap="BlogMapping">select * from blog<where><if test="title != null">and blog_title = #{title}</if><if test="author != null">and blog_author = #{author}</if></where>
</select>

**choose:**传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,

<select id="queryBlogChoose" parameterType="map" resultMap="BlogMapping">select * from blog<where><choose><when test="title != null">blog_title = #{title}</when><when test="author != null">and blog_author = #{author}</when><otherwise>and blog_views = #{views}</otherwise></choose></where>
</select>

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

<update id="updateBlog" parameterType="map">update blog<set><if test="title !=null">blog_title =#{title},</if><if test="author != null">blog_author = #{author},</if></set>where blog_id = #{id}
</update>

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面执行逻辑代码

12.4、动态SQL片段

有的时候我们会把一些公共的部分抽取出来方便复用

<update id="updateBlog" parameterType="map">update blog<set><include refid="if-tilte-author"/></set>where blog_id = #{id}
</update><sql id="if-tilte-author"><if test="title !=null">blog_title =#{title},</if><if test="author != null">blog_author = #{author},</if>
</sql>

结果与上例一样

注意事项:

  • 最好基于单表定义SQL片段
  • 不要存在where标签

12.5、foreach

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

<select id="selectPostIn" resultType="domain.blog.Post">SELECT *FROM POST PWHERE ID in<foreach item="item" index="index" collection="list"open="(" separator="," close=")">#{item}</foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

13、缓存

13.1、简介

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

13.2、MyBatis缓存

  • Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大提升查询效率
  • Myabtis默认提供了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(sqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启,他是基于namespace级别的缓存
    • 为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存

13.3、一级缓存

  • 一级缓存也叫本地缓存:sqlSession(从开启到sqlSession.close())
    • 与数据库用一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果要获取想通的数据,直接从缓存里面拿,没有必要再去查询数据库
  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

缓存失效情况:

  1. 查询不同的东西
  2. 增删改操作
  3. 手动清理缓存sqlSession.clearCache()

小结:一级缓存默认开启,只在一次SqlSession中有效,无法关闭。一级缓存相当于一个Map

13.4、二级缓存

  • 二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存:
  • 工作机制:
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,一级缓存就消失了。不过我们想要的是,会话关闭之后,一级缓存的数据被保存在二级缓存之中
    • 新的会话查询消息,可以直接从二级缓存中读取内容
    • 不同的mapper查处的数据会放在对应的缓存中

步骤:

  1. 开启二级缓存

    <settings><!--显示开启缓存--><setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 在*Mapper.xml中增加标签

    <cache/>
    <!--或者-->
    <cacheeviction="FIFO" flushInterval="60000"size="512"readOnly="true"/>
    
    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  3. 也可以在查询标签中设置

    <select useCache="true">select * from Student;
    </select>
    

注意:pojo最好实现序列化接口

13.5、缓存顺序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzT7NAgi-1588921857563)(MyBatis.assets/截屏2020-04-28 下午4.27.34.png)]

缓存顺序:

  1. 先看二级缓存中有没有
  2. 在看一级缓存中有没有
  3. 查询数据集

参考文献:本篇学习笔记主要总结于bilibili up主 “狂神学Java” 的系列视频— 【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

  相关解决方案