MyBatis

MyBatis

什么是MyBatis

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

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis 。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)

框架的诞生是为了方便程序员的,可以简化代码和方便项目管理。

官方文档https://mybatis.org/mybatis-3/zh/index.html

特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

第一个Mybatis程序

项目结构图

配置环境-->编写代码-->测试代码

配置环境

  • 新建一个普通的Maven项目
  • 将改项目下的src目录删除,然后新建子模块。
  • 在父级模块的pom.xml中导入依赖
<dependencies>
    <dependency>
        <!--Java连接数据库支持-->
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <!--Mybatis框架依赖-->
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <dependency>
        <
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.8.0-M1</version>
        <scope>test</scope>
    </dependency>
</dependencies>
  • 配置资源导出路径

在父级项目的pom.xml文件中添加:

<!--资源导出路径,不配置的话,在用流的时候可能找不到资源文件-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>

编写代码

  • 编写实体类
package com.bin.pojo;
public class User {
    private int id;
    private String username;
    private String pwd;

    public User() {
    }

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

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPwd() {
        return pwd;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • 编写工具类
package com.bin.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
//固定代码,可以直接引用
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static{
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获得SQLSession对象
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}
  • 编写核心配置文件
<?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.jdbc.Driver"/>
          <!--URL-->
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false" />
          <!--用户名-->
        <property name="username" value="root"/>
          <!--密码-->
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/bin/mapper/UserMapper.xml"/>
  </mappers>
</configuration>
  • 编写Mapper

    • UserMapper接口
    package com.bin.mapper;
    import com.bin.pojo.User;
    import java.util.List;
    public interface UserMapper {
        public List<User> getUserList();
    }
    
    • UserMapper.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--注意用全限类名-->
    <mapper namespace="com.bin.mapper.UserMapper">
        <!--id:方法名,resultType:指定泛型,注意用全限类名-->
        <select id="getUserList" resultType="com.bin.pojo.User">
            <!--具体的SQL语句-->
        select * from user;
      </select>
    </mapper>
    

    写完UserMapper.xml后,别忘了在核心配置文件中mappers处添加对于的mapper。

测试程序

  • Test.java
package com.bin;

import com.bin.mapper.UserMapper;
import com.bin.pojo.User;
import com.bin.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class Test {

    @org.junit.Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        //记得关闭,非常重要!!!
        sqlSession.close();
    }
}

测试结果

可能会出现的异常

  • java.io.IOException: Could not find resource mybatis-config

这是折磨了我一天的异常,我真的是服了,网上找了很多方法都不管用,最后才发现自己的核心配置文件没有用xml格式储存

心态有点小崩。。。。

  • java.lang.ExceptionInInitializerError

这个异常大多情况下是配置文件写的有问题,不管是Mapper.xml还是核心配置文件,根据下面提示定位修改即可。

CRUD

UserMapper----->UserMapper.xml----->测试类

Mapper.xml文件

  • namespace:命名空间,放接口的全限类名
  • id:放方法名
  • resultType:返回值类型,如果是集合则返回集合指定的泛型,全限类名
  • parameterType:参数类型,如果是放实体类,需要提供全限类名

CRUD

  • UserMapper
int insertUser(User user);
  • UserMapper.xml
<insert id="insertUser" parameterType="com.bin.pojo.User">
    <!--#{}用该表达式来传参-->
    insert into user (id,username,pwd) values(#{id},#{username},#{pwd})
</insert>
  • 测试类
public class Test {
    @org.junit.Test
    public void insertTest() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(4,"赵六","123456"));
        //提交事务
        sqlSession.commit();
        //记得关闭
        sqlSession.close();
    }

  • UserMapper
int deleteUser(int id);
  • UserMapper.xml
<insert id="deleteUser" parameterType="int">
    <!--#{}用该表达式来传参-->
    delete from user where id = #{id}
</insert>
  • 测试类
public class Test {
    @org.junit.Test
    public void deleteUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(1);
        //提交事务
        sqlSession.commit();
        //记得关闭
        sqlSession.close();
    }

  • UserMapper
int updateUser(User user);
  • UserMapper.xml
<update id="updateUser" parameterType="com.bin.pojo.User">
	update user set username = #{username},pwd = #{pwd} where id = #{id}
</update>
  • 测试类
public class Test{
    @org.junit.Test
    public void test() {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        mapper.updateUser(new User(1,"李四","122334"));
        //提交事务
        sqlsession.commit();
        //关闭连接
        sqlsession.close();
    }
}

注意事项

  • 用使用增删改时,记得提交事务,否则数据库中的数据不会得到更新。

万能的Map

每次用实体类作为参数调用dao层的方法有时会显得比较笨拙,比如说:我想通过id来修改用户的密码,如果是用实体类作为参数传入的话,还需要附带一些其他没用的字段/属性,生日,注册时间,年龄。。。。这些跟业务并没有什么联系,想实现的业务仅仅是涉及到两个字段/属性而已,如果是这种情况,可以使用万能的Map,将Map对象作为参数传入,将所需要的字段/属性放作为Key值放入该Map即可。

  • UserMapper
int updatePassword(Map<String,Object> map);
  • UserMapper.xml
<update id="updatePassword" parameterType="map">
    update user set pwd = #{pwd} where id = #{id}
</update>
  • 测试类
@org.junit.Test
    public void updatePassword(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("id",2);
    map.put("pwd","123456");
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.updatePassword(map);
    sqlSession.commit();
    sqlSession.close();
}

模糊查询

查询一个名字带有张的人,如何实现?

  • UserMapper
List<User> getUserListByLike(String username);
  • UserMapper.xml
<select id="getUserListByLike" parameterType="string" resultType="hello">
    select * from user where username like #{username}
</select>
  • 测试类
@org.junit.Test
    public void getUserListByLike() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //关键代码,在字符串中首尾嵌入两个“%”
    List<User> userList = mapper.getUserListByLike("%张%");
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

占位符

占位符会出现在Mapper.xml文件中的sql语句

{}里面填法:

  • 传参传的是单个基本数据类型:使用该变量名
  • 传参传的是对象:该对象的属性
  • 传参传的是Map对象:该Map中的Key

配置解析

核心配置文件

顺序

不按照特定顺序编写核心配置文件会报以下异常:

Error building SqlSession.

Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 26; columnNumber: 17; 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"。

也就是表明了,核心配置文件必须按照

properties-->settings-->typeAliases-->typeHandlers-->objectFactory-->objectWrapperFactory-->reflectorFactory-->plugins-->environments-->databaseIdProvider-->mappers

顺序进行配置

环境配置

  • evironments:

可以配置多个环境,但是每个SQLFactory每次只能选择一个环境,可以通过environments中的default属性来选择相应的环境。

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}" />
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
    <environment id="development2">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}" />
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>
  • 事务管理transactionManager

type="[JDBC|MANAGED]"

  • 数据源dataSource

type="[UNPOOLED|POOLED|JNDI]"

属性properties

可以通过外部的properties文件来对核心配置文件中的相关属性进行配置。

  • mybatis-config.xml
<properties resource="db.properties" />
  • db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&allowPublicKeyRetrieval=true
username=root
password=root
  • mybatis-config.xml
<environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
        <!--在此处只需要用${属性名}-->
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}" />
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </dataSource>
</environment>

类型别名

在使用Mapper.xml文件时,有时需要用到实体类的权限类名,比如:resultType="com.bin.pojo.User",但每次这样写会显得过于冗余,为此可以在核心配置文件中引入别名。

typeAliases

  • typeAlias
<typeAliases>
    <typeAlias alias="hello" type="com.bin.pojo.User" />
</typeAliases>
  • package(推荐使用)
<package name="com.bin.pojo"/>

使用包名,可以将特定包下的类按照一定规则起别名,实体类:若没有用别名注解声明别名,则别名为首字母小写的实体类类名,若有则按照注解声明的别名。

  • 使用场景

在实体类较少时可以用typeAlias,实体类较多可以用package。

映射器

映射器的作用是告诉Mybatis去哪里能找到Mapper.xml,每写一个Mapper.xml,都要有去核心配置文件配置的习惯。

mappers

  • resource属性
<mapper resource="com.bin.dao.UserMapper.xml"/>

这是一种较为官方的做法。。。

  • 将特定包内的映射器接口实现全部注册为映射器
<package name="org.mybatis.builder"/>

映射器接口实现过多的情况,用该方法好像挺不错的

  • 使用映射器接口实现类的完全限定名(推荐)
<mapper class="com.bin.dao.UserMapper"/>

使用该方法,需要满足以下两个要求

1.映射器名字需要跟所实现的接口名一致

2.映射器与对应接口需在同一个包下

生命周期和作用域

不同的作用域和生命周期类别是十分重要的,如果错误的使用会导致很严重的并发问题。

  • SqlSessionFactoryBuilder

该类可以被实例化、使用和丢弃,一旦创建了SQLSessionFactory就不需要它了,因此SqlSessionFactoryBuilder最合适的作用域是方法作用域,可以用SQLSessionFactoryBuilder来创建多个SqlSessionFactory,但是一旦创建完,就不需要保留它了

  • SqlSessionFactory

SqlSessionFactory一旦被创建就应该一直存在,没有理由丢弃或者去创造另一个实例,因此最合适的作用域是应用作用域,使用SQLSessionFactory最佳的实践方法就是在应用运行期间不要被创建多次,最简单的实现方案是单例模式和静态单例模式。

  • SqlSession

每个线程都应该有自己的SQLSession实例,SQLSession是非线程安全的,因此是不能被共享的,因此最佳的作用域是方法作用域,绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。用完及时关闭

解决问题

属性名和字段名不一致的问题

首先实体类与数据库中的表是一一对应的。

表名-->实体类的类名

字段名-->实体类的属性名

但是也会遇到这种情况,数据库中的字段有时会倾向于以这种方式命名create_date,而在实体类中采用驼峰命名法,createDate。

先测试一下属性名与字段名不一致会出现什么问题。

  • 实体类

实体类中的密码:private String password

  • 数据库

数据库表中的字段:pwd

确定一下表中信息是否正常:

  • getUserList()
@org.junit.Test
    public void test() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUserList();
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

解决方案

  • 取别名
select id,username,pwd as password from user;
  • resultMap
<resultMap id="resultMap1" type="user">
    <result column="pwd" property="password"></result>
</resultMap>
<select id="getUserList" resultMap="resultMap1">
    select * from user
</select>
<select id="getUserList" resultMap="resultMap1">
    select * from user
</select>

注意

1.当实体类中只是存在个别属性名与表中字段名不一致的情况,只需要将不一致的属性和字段说明映射关系即可。

2.resultMap 元素是 MyBatis 中最重要最强大的元素

日志

如果一个数据库操作出现了异常,我们就需要排错,日志就是最好的帮手,以前:sout,debug。现在:日志工厂

日志工厂

  • SLF4J
  • LOG4J
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING
  • NO_LOGGING

STDOUT_LOGGING

这个使用较为简单,直接修改核心配置文件即可

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

log4j

简介:

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等.
  • 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用:

  • 导入依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • 在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/bin.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
  • 在mybatis核心配置文件中设置相关属性
<settings>
    <!--注意大小写和空格,一个都不能错-->
	<setting name="logImpl" value="LOG4J"/>
</settings>
  • 测试

简单使用

之前做测试时一直是使用的System.out.println()来测试,现在可以用新的方式。

public class Test01{
    private Logger logger = Logger.getLogger(Test.class);
    @Test
    public void log4jTest() {
        logger.info("info:进入了log4jTest");
        logger.debug("debug:进入了log4jTest");
        logger.error("error:进入了log4jTest");
    }
}

分页

limit实现

  • UserMapper
List<User> getUserListLimit(Map<String,Integer> map);
  • UserMapper.xml
<select id="getUserListLimit" parameterType="map" resultType="user">
	select * from user limit #{startIndex},#{pageSize}	
</select>
  • 测试类
@Test
public void getUserListLimitTest() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Integer> map = new HashMap(String,Integer);
    map.put("startIndex",1);
    map.put("pageSize",3);
    List<User> userList = mapper.getUserListLimit(map);
    for(User user:userList){
        System.out.println(user);
    }
    sqlSession.close();
}

PageHelper插件

以后可能会用到,了解有这个东西存在即可。

用注解进行CRUD

面向接口编程

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

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
  • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
  • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

使用注解进行CRUD

接口--->配置文件--->测试类

  • UserMapper
public interface UserMapper {
    //增
    @Insert("insert into user(id,username,pwd) values(#{id},#{username},#{password})")
    int insertUser(User user);
    //删
    @Delete("delete from user where id = #{id}")
    int deleteUserById(@Param("id") int id);
    //查
    @Select("select * from user")
    List<User> selectUser();
    //改
    @Update("update user set username = #{username},pwd = #{pwd} where id = #{id}")
    int updateUser(User user);
}
  • Mybatis-config.xml
<mappers>
    <mapper class="com.bin.dao.UserMapper"/>
</mappers>
  • 测试类
public class Test01 {
    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(4,"小斌","123123"));
        sqlSession.close();
    }
}

//        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//        List<User> users = mapper.selectUser();
//        for (User user : users) {
//            System.out.println(user);
//        }


//        mapper.deleteUserById(4);


//        mapper.insertUser(new User(4,"小洪","1112223333"));

注意

  • 使用注解进行CRUD时,只需要在接口的方法声明上添加相对应的注解。
  • 当参数类型为基本数据类型或String时,需要添加@Param()注解
  • 当参数类型为引用数据类型时,则不需要添加@Param()注解
  • 注解只能满足简单的SQL语句,因此还是建议使用Mapper.xml文件。

一对多和多对一

根据视角不同:

  • 对于老师而言,一个老师可以有多个学生,这就是一对多
  • 对于学生而言,多个学生可以关联一个老师,这就是多对一

在日常生活也有类似例子:比如学生信息表会出现的,学院 --- 姓名

根据老师与学生关系可以设计两个小案例来说明在Mybatis中一对多和多对一应该如何处理。

一对多

对于该关系,表和实体类有不同做法:

  • 表:利用外键
  • 实体类:在老师的实体类中添加学生List属性

这样,就引出了要解决的核心问题:解决字段名与属性名不匹配的问题。

解决方案:利用resultMap

开始测试

  • 搭建测试环境

    • 在数据库中执行以下sql语句:
    CREATE TABLE `teacher` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    
    INSERT INTO teacher(`id`, `name`) VALUES (1, 秦老师); 
    
    CREATE TABLE `student` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      `tid` INT(10) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `fktid` (`tid`),
      CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, 小明, 1); 
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, 小红, 1); 
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, 小张, 1); 
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, 小李, 1); 
    INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, 小王, 1);
    

  • 创建核心配置文件和db.properties文件

  • 根据表中字段编写相应的实体类

public class Teacher {
    private int id;
    private String name;
    /*划重点这里学问挺深的,老师与学生关系是一对多的,因此在实体类中,学生实体类是正常创建的,
    而老师实体类会因为该关系,在属性中添加学生列表*/
    private List<Student> students;
    public Teacher() {
    }
    public Teacher(int id, String name, List<Student> students) {
        this.id = id;
        this.name = name;
        this.students = students;
    }
    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 List<Student> getStudents() {
        return students;
    }
    public void setStudents(List<Student> students) {
        this.students = students;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", students=" + students +
                '}';
    }
}
public class Student {
    private int id;
    private String name;
    private int tid;
    public Student() {
    }
    public Student(int id, String name, int tid) {
        this.id = id;
        this.name = name;
        this.tid = tid;
    }
    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 int getTid() {
        return tid;
    }
    public void setTid(int tid) {
        this.tid = tid;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 编写获得SqlSessionFactory的工具类

  • TeacherMapper

public interface TeacherMapper {
//    Teacher getTeacher();
    //根据输入的老师id查询出所有学生
    List<Teacher> getTeacher(@Param("tid") int id);
}
  • TeacherMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bin.dao.TeacherMapper">
    <select id="getTeacher" resultMap="TeacherStudent">
    select t.id as tid,t.name as tname,s.name as sname from student s,teacher t where s.tid = t.id
    </select>
    <resultMap id="TeacherStudent" type="teacher">
        <result property="id" colum="tid" />
        <result property="name" colum="tname"/>
        <!--可以这样理解,List是一个集合,因此这里要使用集合标签-->
        <collection property="students" ofType="student">
        	<result property="name" colum="sname"/>
        </collection>
    </resultMap>
</mapper>
  • 将Mapper注册到核心配置文件中

运行结果

多对一

同样,对于该关系,表和实体类有不一样的做法:

  • 表:利用外键

  • 实体类:将学生与老师关联起来,或者说是将老师类封装到学生实体类中

同样,也会引出核心问题:字段名与实体类属性不匹配

解决方案:利用resultMap

开始测试

  • 搭建测试环境与一对多一致

  • 编写实体类

public class Teacher {
    private int id;
    private String name;
    public Teacher(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
    public Teacher() {
    }
    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 class Student {
    private int id;
    private String name;
    //维持Teacher类的引用
    private Teacher teacher;

    public Student() {
    }
    public Student(int id, String name, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.teacher = teacher;
    }
    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 Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

  • StudentMapper
public interface StudentMapper {
    //查询学生id,名字以及对应的老师
    List<Student> getStudentList();
}
  • StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bin.dao.StudentMapper">
    <select id="getStudentList" resultMap="StudentTeacher">
    	select s.id as sid,s.name as sname,t.name as tname from student s,teacher t where s.tid = t.id
    </select>
    <resultMap id="StudentTeacher" type="student">
        <result property="name" colum="sname"/>
        <result property="id" colum="sid"/>
        <association property="teacher" type="teacher">
        	<result property="name" colum="tname"/>
        </association>
    </resultMap>
</mapper>
  • 将Mapper注册到核心配置文件中

运行结果

注意

一对多和多对一,在实体类和在数据库表中分别是怎么进行处理的?

一对多:

  • 表:外键
  • 实体类:利用集合

多对一:

  • 表:外键
  • 实体类:利用关联关系

由于实体类与表的处理是不同的,因此就必定会存在数据库表字段名与实体类属性不一致的问题,为此就MyBatis就引入了解决方案。

了解这一点比知道解决方案更为重要

动态SQL

回顾一下痛苦经历

要实现一个简单的连表查询,要用到字符串追加,然后还要设定参数。

现在,用官方的话来说就是:动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

元素种类:

  • if:若

  • choose (when, otherwise):用于条件中,类似于switch...case...

  • trim (where, set):用于update语句中

  • foreach:用于条件中

if

  • BlogMapper
//通过If获得数据
List<Blog> getBlogsIf(Map map);
  • BlogMapper.xml
<select id="getBlogsIf" parameterType="map" resultType="Blog">
    select * from blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    <if test="views != null">
        and views > #{views}
    </if>
</select>

但是,在sql语句中出现1=1有些不规范

为此,更加合理的做法应该是:

<select id="getBlogsWhereIf" parameterType="map" resultType="blog">
    select * from blog
    <!--若第一个条件为null,where会智能地把and去掉-->
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
        <if test="views != null">
            and views > #{views}
        </if>
    </where>
</select>

choose

  • BlogMapper
//通过Choose和when来获得数据
List<Blog> getBlogListByChooseWhen(Map map);
  • BlogMapper.xml
<select id="getBlogListByChooseWhen" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                author = #{author}
            </when>
            <otherwise>
                1 = 1
            </otherwise>
        </choose>
    </where>
</select>
  • 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("title","MyBatis");
map.put("author","狂神说");
List<Blog> blogs = mapper.getBlogListByChooseWhen(map);
for (Blog blog : blogs) {
    System.out.println(blog);
}
sqlSession.close();

set

  • BlogMapper
//通过where和set来获得数据
int updateBlogByWhereSet(Map map);
  • BlogMapper.xml
<update id="updateBlogByWhereSet" parameterType="map">
    update blog
    <!--可以更新任意多的字段-->
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author},
        </if>
        <if test="views !=null">
            views = #{views}
        </if>
    </set>
    <where> id = #{id}</where>
</update>
  • 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("id","908b088d5a6f4cdc89b59bbe6e81e4d3");
map.put("title","JavaSE");
map.put("author","小斌");
map.put("views",2000);
mapper.updateBlogByWhereSet(map);
sqlSession.commit();
sqlSession.close();

foreach

  • BlogMapper
//通过foreach来获得数据
List<Blog> selectByForeach(Map map);
  • BlogMapper.xml
<select id="selectByForeach" resultType="blog">
    select * from blog
    <where>
        views in
        <!--collection:集合名,open:以...开始,close:以...闭合,separator:分隔符,item:集合内元素-->
        <foreach collection="viewsList" open="(" close=")" separator="," item="views">
            #{views}
        </foreach>
    </where>
</select>
  • 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
ArrayList<Integer> viewsList = new ArrayList<Integer>();
viewsList.add(9999);
viewsList.add(1000);
map.put("viewsList",viewsList);
List<Blog> blogs = mapper.selectByForeach(map);
for (Blog blog : blogs) {
    System.out.println(blog);
}

缓存

什么是缓存?

有时候,某些数据是会经常需要访问的,像硬盘内部的缓存(暂存器的一种)会将读取比较频繁的一些数据存储在缓存中,再次读取时就可以直接从缓存中直接传输。

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

MyBatis中的缓存

MyBatis中的缓存可以分为两类:一级缓存和二级缓存。

一级缓存

作用域:一个SqlSession会话期间,也就是SqlSession的存活周期。

测试

  • UserMapper
//根据id查询用户
User getUserById(@Param("id") int id);

//更新用户
int updateUser(Map map);
  • UserMapper.xml
<select id="getUserById" resultType="user">
    select * from user where id = #{id}
</select>

<update id="updateUser" parameterType="map">
    update user
    <set>
        <if test="username != null">
            username = #{username},
        </if>
        <if test="pwd != null">
            pwd = #{pwd}
        </if>
    </set>
    where id = #{id}

</update>
  • 测试类
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1);
System.out.println(user1);
Map map = new HashMap();
map.put("id",2);
map.put("username","djalkdja0");
map.put("pwd","dadjlka");
//测试在两个查询之间插入一条更新语句
mapper.updateUser(map);
//清空缓存
sqlSession.clearCache();
System.out.println("===============================");
User user2 = mapper.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
  • 结果

正常情形下,对同一个id的用户数据进行查询,只执行了一条sql语句,也就代表了一级缓存存在的机制;当在两条查询之间插入一条更新语句或者清空缓存的语句时,执行了两条相同的sql语句。

二级缓存

由于一级缓存的作用域实在太小,为此引入了二级缓存。

作用域:基于namespace级别的缓存,一个Mapper文件对应一个缓存区。

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

测试

  • 配置核心配置文件
<!--显式配置缓存-->
<setting name="cacheEnabled" value="true"/>
  • 配置UserMapper.xml文件
<cache />
  • 实体类User实现可序列化接口(这一步很重要,别忘了)

  • 测试类

@Test
public void test02(){
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user1 = mapper1.getUserById(1);
    System.out.println(user1);
    //会话结束后,会将一级缓存的内容存到二级缓存中去
    sqlSession1.close();
    System.out.println("===============================");
    User user2 = mapper2.getUserById(1);
    System.out.println(user2);
    System.out.println(user1 == user2);
    sqlSession2.close();
}

MyBatis中的缓存机制

在开启二级缓存后

动态SQL的练习题

根据接口中的方法编写对应的SQL语句:

  • BillMapper.xml
<select id="getBillCountByProviderId" >
    select count(bill) from bill where providerId = #{providerId}
</select>

<insert id="add" parameterType="bill">
    insert into bill (billCode,productName,productDesc,productUnit,productCount,totalPrice,isPayment,createBy,creationDate,modifyBy,modifyDate,providerId)
    values(#{billCode},#{productName},#{productDesc},#{productUnit},#{productCount},#{totalPrice},#{isPayment},#{createBy},#{creationDate},#{modifyBy},#{modifyDate},#{providerId})
    where id = #{id}
</insert>

<select id="getBillList" resultType="Bill">
    select * from bill
    <where>
        <if test="productName != null">
            productName = #{productName}
        </if>
        <if test="providerId != null">
            and providerId = #{providerId}
        </if>
        <if test="isPayment ! = null">
            and isPayment = #{isPayment}
        </if>
    </where>
    limit #{from},#{pageSize}
</select>

<select id="getBillCount">
    select count(bill) from bill
    <where>
        <if test="productName != null">
            productName = #{productName}
        </if>
        <if test="providerId != null">
            and providerId = #{providerId}
        </if>
        <if test="isPayment != null">
            and isPayment = #{isPayment}
        </if>
    </where>
</select>

<delete id="deleteById">
    delete from bill where id = #{id}
</delete>

<select id="getBillById" resultType="Bill">
    select * from user where id = #{id}
</select>

<update id="modify" parameterType="Bill">
    update user
    <set>
        <if test="billCode != null">
            billCode = #{billCode}
        </if>
        <if test="productName != null">
            productName = #{productName}
        </if>
        <if test="productDesc != null">
            productDesc = #{productDesc}
        </if>
        <if test="productUnit != null">
            productUnit = #{productUnit}
        </if>
        <if test="produntCount != null">
            productCount = #{productCount}
        </if>
        <if test="totalPrice != null">
            totalPrice = #{totalPrice}
        </if>
        <if test="isPayment != null">
            isPayment = #{isPayment}
        </if>
        <if test="createdBy != null">
            createdBy = #{createdBy}
        </if>
        <if test="creationDate != null">
            creationDate = #{creationDate}
        </if>
        <if test="modifyBy != null">
            modifyBy = #{modifyBy}
        </if>
        <if test="modifyDate">
            modifyDate = #{modifyDate}
        </if>
        <if test="providerId != null">
            providerId = #{providerId}
        </if>
    </set>
    where id = #{id}
</update>

<delete id="deleteBillByProviderId">
    delete from bill where providerId = #{providerId}
</delete>	

posted @ 2021-06-02 13:23  Code_Ice  阅读(57)  评论(0)    收藏  举报