2021-10-28 MyBatis学习

Mybatis

框架

框架( Framework )是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。

框架( Framework )是构成一类特定软件可复用设计的一组相互协作的类。框架规定了你的应用的体系结构。它定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者或实现者能集中精力于应用本身的特定细节。

ORM

ORM,Object-Relational Mapping,对象关系映射,它的作用是在关系型数据库和对象之间作一个映射处理。

JDBC的缺点:需要手动的完成面向对象的Java语言、面向关系的数据库之间数据的转换,代码繁琐无技术含量,影响了开发效率。

ORM框架就是在面向对象语言和关系数据库之间搭建一个桥梁。这样我们在具体的操作数据库的时候,只要像平时操作对象一样操作它就可以了,ORM框架会根据映射完成对数据库的操作,就不需要再去和复杂的SQL语句打交道了。

Mybatis简介

Mybatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。Mybatis是一个优秀的基于java的持久层框架。

Mybatis的主要设计目的就是让我们对执行SQL语句时对输入输出的数据管理更加方便,所以方便地写出SQL和获取SQL的执行结果才是Mybatis的核心竞争力。

MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的 POJO(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录。

简单的说:MyBatis是一个半自动ORM框架,其本质是对JDBC的封装。使用MyBatis重点需要程序员编写SQL命令,不需要写一行JDBC代码。

Mybatis的功能架构分为三层:

1、API接口层:提供给外部使用的接口API

2、数据处理层:根据调用的请求完成一次数据库操作

3、基础支撑层:负责最基础的功能支撑

Mybatis与 Hibernate的比较

Hibernate是一个全自动的ORM框架。因为 Hibernate创建了Java对象数据库表之间的完整映射,可以完全以面向对象的思想来操作数据库,程序员不需要手写SQL语句,而MyBatis中还需要手写SQL语句,所以是半自动化的,工作量要大于Hibernate。

半自动化的 Mybatis 更受欢迎的原因

MyBatis需要手写SQL语句,其工作量要大于Hibernate。但是也正是由于自定义SQL语句,所以其灵活性、可优化性就超过了Hibernate。

Hibernate封装了SQL语句,由开发者对对象操作,Hibernate来生成SQL语句。虽然也可以通过映射配置来控制生成的SQL语句,但是对于要生成复杂的SQL语句,很难实现,或者实现后导致性能的丢失。

而MyBatis将手写SQL语句的工作丢给开发者,可以更加精确的定义SQL,更加灵活,也便于优化性能。完成同样功能的两条SQL语句的性能可能相差十几倍到几十倍,在高并发、快响应要求下的互联网系统中,对性能的影响更明显。

MyBatis对存储过程可提供很好的支持。MyBatis的开发工作量大不意味着学习成本大。对于新手,学习 Hibernate时间成本比Mybatis大很多,Mybatis很快就上手了。

总之,因为MyBatis具有封装少、映射多样化、支持存储过程、可以进行SQL语句优化等特点,符合互联网高并发、大数据、高性能、高响应的要求,使它取代Hibernate成为了Java互联网中首选的持久框架。而对于对性能要求不高的比如内部管理系统、ERP(Enterprise Resource Planning)等可以使用Hibernate。

Mybatis的基本使用

1、jar包介绍

在这里插入图片描述

2、核心API介绍

  • SqlSessionFactoryBuilder
    作用:使用构建者模式创建SqlSessionFactory 接口对象

  • SqlSessionFactory
    可以被认为是一个数据库连接池,作用:创建SqlSession接口对象

  • SqlSession
    SqlSession就相当于一个数据库连接(Connection对象),可以在一个事务里面执行多条SQL,然后通过它的commit、rollback 方法提交或者回滚事务

  • Mapper

    映射器。由一个Java接口和XML文件(或者注解构成),需要给出对应的SQL和映射规则,负责发送SQL去执行并返回结果

3、生命周期

  • SqlSessionFactoryBuilder:
    该类用来创建SqlSessionFactory对象,当SqlSessionFactory对象被创建后,SqlSessionFactoryBuilder就失去了作用,所以它只能存在于创建SqlSessionFactory的方法中,而不要让其长期存在。因此SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域

  • SqlSessionFactory:
    SqlSessionFactory的生命周期存在于整个MyBatis的应用之中,所以一旦创建了SqlSessionFactory,就要长期保存它,直至不再使用MyBatis应用,可以认为SqlSessionFactory 的生命周期就等同于MyBatis的应用周期。由于SqlSessionFactory被认为数据库连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此SqlSessionFactory是一个单例,让它在应用中被共享

  • SqlSession:
    SqlSession应该存活在一个业务请求中,处理完整个请求后,应该关闭连接,让它归还SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以try…catch…finally.….语句来保证其正确关闭。所以SqlSession 的最佳的作用域是请求或方法作用域

  • Mapper:
    由于SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于SqlSession的生命周期。Mapper代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。

4、配置文件

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

  • 全局配置文件
  • 映射配置文件

全局配置文件

全局配置文件的名称是自定义的,在JavaProject 项目中需要放到src目录下。
全局配置文件的作用是完成一些全局性的配置,如:对Mybatis框架的设置、别名设置、环境设置、指定映射配置文件等相关配置。

  • properties标签

  • settings标签

  • typeAliases标签 给POJO类起别名、指定包名等

  • environments标签 配置多环境

    • transactionManager标签 配置事务 JDBC/MANAGED不做事务处理
    • dataSource标签 配置数据源 UNPOOLED直连/POOLED池连/JNDI
  • mapper标签

    • 相对于类路径指定映射配置文件

      <mappers>
          <mapper resource="com/bjsxt/mapper/UserMapper.xml"/>
      </mappers>
      
    • 使用 file:///协议指定映射文件

      <mappers>
          <mapper url="file:///D:\code\mybatis\src\com\bjsxt\mapper\UserMapper.xml"/>
      </mappers>
      
    • 指定映射接口

      <mappers>
          <mapper class="com.bjsxt.mapper.UserMapper"/>
      </mappers>
      
    • 通过包名指定映射接口

      <mappers>
          <package name="com.bjsxt.mapper"/>
      </mappers>
      

映射配置文件

映射配置文件主要是用来编写Sql语句的,结果集的映射关系的指定,以及缓存的一些配置等等。

在这里插入图片描述

Mybatis入门案例

1、搭建项目和配置环境

1、创建表与添加DTD约束文件
CREATE TABLE `users` (
  `userid` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `usersex` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

dtd文件下载地址

在这里插入图片描述

2、创建普通java项目

在这里插入图片描述

3、添加14个jar包

在这里插入图片描述

4、创建实体
public class Users {
    private int userid;
    private String username;
    private String usersex;

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public String getUsersex() {
        return usersex;
    }

    public void setUsersex(String usersex) {
        this.usersex = usersex;
    }
}
5、创建db.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bjsxt?useSSL=false
jdbc.username=root
jdbc.password=1615
6、创建全局配置文件
<?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文件-->
    <properties resource="db.properties"/>
    <!--环境配置-->
    <environments default="development">
        <environment id="development">
            <!--配置事务-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射配置文件-->
    <mappers>
        <!--使用相对路径引入-->
        <mapper resource="com/bjsxt/mapper/UsersMapper.xml"/>
    </mappers>
</configuration>
7、创建映射配置文件
<?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.bjsxt.mapper.UsersMapper">
</mapper>

2、查询数据

修改映射配置文件

<?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.bjsxt.mapper.UsersMapper">
    <!--查询所有用户-->
    <select id="selectUsersAll" resultType="com.bjsxt.pojo.Users">
        select * from users
    </select>
</mapper>

创建UsersDao接口

public interface UsersDao {
    List<Users> selectUsersAll() throws IOException;
}

创建UsersDao接口实现类

public class UsersDaoImpl implements UsersDao {
    /**
     * 查询所有用户
     * @return
     */
    @Override
    public List<Users> selectUsersAll() throws IOException {
        // 创建SqlSessionFactory对象
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 创建SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession对象下的API完成对数据库的操作
        List<Users> list = sqlSession.selectList("com.bjsxt.mapper.UsersMapper.selectUsersAll");
        // 关闭SqlSession对象
        sqlSession.close();
        return list;
    }
}

创建测试类

package com.bjsxt.test;

import com.bjsxt.dao.impl.UsersDaoImpl;
import com.bjsxt.pojo.Users;

import java.io.IOException;
import java.util.List;

public class Test {
    public static void main(String[] args) throws IOException {
        UsersDaoImpl usersDao = new UsersDaoImpl();
        List<Users> list = usersDao.selectUsersAll();
        for(Users user:list){
            System.out.println( user.getUserid()+"\t"+user.getUsername()+"\t"+user.getUsersex());
        }
    }
}

【注意事项】某些版本mysql要求安全连接,通过useSSL=false忽略

如果jdbc.url=jdbc:mysql://localhost:3306/bjsxt,会报错无法查询到结果;

如果jdbc.url=jdbc:mysql://localhost:3306/bjsxt?useSSL=false,能够查询到结果,仍然有警告信息。

3、根据用户ID查询数据

修改映射配置文件

<!--根据用户ID查询-->
<select id="selectUsersById" parameterType="int" resultType="com.bjsxt.pojo.Users">
    select * from users where userid = #{suibian}
</select>

修改UsersDao接口

Users selectUsersById(int userid) throws IOException;

修改UsersDao接口实现类

/**
 * 根据用户ID查询
 * @param userid
 * @return
 * @throws IOException
 */
@Override
public Users selectUsersById(int userid) throws IOException {
    // 创建SqlSessionFactory对象
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 创建SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 通过SqlSession对象下的API完成对数据库的操作
    Users user = sqlSession.selectOne("com.bjsxt.mapper.UsersMapper.selectUsersById",userid);
    // 关闭SqlSession对象
    sqlSession.close();
    return user;
}

修改测试类

UsersDaoImpl usersDao = new UsersDaoImpl();
Users user = usersDao.selectUsersById(1);
System.out.println( user);

4、Mybatis中的参数绑定

在映射配置文件中向SQL语句中绑定参数的语法结构有#{ }和${ }两种方式。

#{ }和$ }的区别

  • #{ }解析为一个JDBC预编译语句的参数标记符占位符?,使用该方式可避免SQL注入

  • 仅 仅 为 一 个 纯 碎 的 S t r i n g 替 换 , 在 M y b a t i s 的 动 态 S Q L 解 析 阶 段 将 会 进 行 变 量 替 换 。 { }仅仅为一个纯碎的String替换,在 Mybatis的动态SQL解析阶段将会进行变量替换。 StringMybatisSQL{ }在预编译之前已经被变量替换了,会存在SQL注入问题。如:

    select * from ${tablename} where ${columns} = #{suibian}
    

5、创建Mybatis的工具类

ThreadLocal介绍

ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

使用ThreadLocal存储SqlSession

如果多个DML操作属于一个事务,因为commit()和rollback()都是由SqlSession完成的,所以必须保证使用同一个SqlSession。但是多个不同的DML操作可能在不同类的不同方法中,每个方法中要单独的获取SqlSession。如何在多个DML操作之间使用同一个SqlSession呢?可以使用ThreadLocal来存储。保证一个线程中的操作使用的都是同一个SqlSession。

  • SqlSessionFactory单例模式
  • 使用ThreadLocal对象来存储SqlSession对象
package com.bjsxt.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 ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();
    private static SqlSessionFactory sqlSessionFactory = null;
    static{
        // 创建SqlSessionFactory对象
        InputStream is = null;
        try{
            is = Resources.getResourceAsStream("mybatis-config.xml");
        }catch(IOException e){
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    }

    // 获取sqlSession
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = threadLocal.get();
        if(sqlSession == null){
            sqlSession = sqlSessionFactory.openSession();
            threadLocal.set(sqlSession);
        }
        // 不用再执行sqlSession = threadLocal.get();
        return sqlSession;
    }

    // 关闭sqlSession
    public static void closeSqlSession(){
        SqlSession sqlSession = threadLocal.get();
        if(sqlSession != null){
            sqlSession.close();
            threadLocal.set(null);
        }
    }
}

6、实现DML操作

Mybatis的事务提交方式

默认为手动提交,在JDBC中事务默认提交方式为自动提交

手动提交事务(默认)

sqlsession sqlsession = sqlsessionFacotry.openSession();

自动提交事务

sqlsession sqlsession = sqlSessionFacotry.openSession(true);
1、添加用户

修改配置文件

<!--添加用户-->
<insert id="insertUsers">
    insert into users values(default,#{username},#{usersex})
</insert>

修改UsersDao接口

void insertUsers(Users users);

修改UsersDao接口实现类

/**
 * 添加用户
 * @throws IOException
 */
@Override
public void insertUsers(Users users){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    sqlSession.insert("com.bjsxt.mapper.UsersMapper.insertUsers",users);
    // 由于很可能有多个DML操作,所以提交事务和关闭sqlSession的操作应放在业务层,而不是放在Dao层
}

修改UsersService接口

public interface UsersService {
    void addUsers(Users users);
}

修改UsersService接口实现类

public class UsersServiceImpl implements UsersService {
    /**
     * 添加用户
     * @param users
     */
    @Override
    public void addUsers(Users users) {
        /*
        1、业务层首先调用了getSqlSession()方法获得了sqlSession
        2、Dao层获得的就是这个sqlSession
        所以才实现事务统一操作
         */
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try{
            UsersDaoImpl usersDao = new UsersDaoImpl();
            usersDao.insertUsers(users);
            sqlSession.commit();
        }catch(Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally{
            MybatisUtils.closeSqlSession();
        }
    }
}

创建测试类

public class AddUserTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        Users users = new Users();
        System.out.println(users);
        users.setUsername("faker");
        users.setUsersex("male");
        usersService.addUsers(users);
    }
}
2、更新用户

修改配置文件

<!--预更新用户的查询-->
<select id="selectUsersById2" resultType="com.bjsxt.pojo.Users">
    select * from users where userid = ${userid}
</select>
<!--更新用户操作-->
<update id="updateUsersById">
    update users set username = #{username},usersex = #{usersex} where userid = #{userid}
</update>

修改UsersDao接口

Users selectUsersById2(int userid);
void updateUsersById(Users users);

修改UsersDao接口实现类

/**
 * 预更新用户的查询
 * @param userid
 * @return
 */
@Override
public Users selectUsersById2(int userid) {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    Users users = sqlSession.selectOne("com.bjsxt.mapper.UsersMapper.selectUsersById2", userid);
    return users;
}

/**
 * 更新用户
 * @param users
 */
@Override
public void updateUsersById(Users users) {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    int update = sqlSession.update("com.bjsxt.mapper.UsersMapper.updateUsersById", users);
}

修改UsersService接口

Users preUpdateUsers(int userid);
void modifyUsers(Users users);

修改UsersService接口实现类

/**
 * 预更新用户的查询
 * @param userid
 * @return
 */
@Override
public Users preUpdateUsers(int userid) {
    Users users = null;
    try{
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersDaoImpl usersDao = new UsersDaoImpl();
        users = usersDao.selectUsersById2(userid);
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        MybatisUtils.closeSqlSession();
    }
    return users;
}

/**
 * 更新用户
 * @param users
 */
@Override
public void modifyUsers(Users users) {
    // 注意作用域
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    try{
        UsersDaoImpl usersDao = new UsersDaoImpl();
        usersDao.updateUsersById(users);
        sqlSession.commit();
    }catch(Exception e){
        e.printStackTrace();
        sqlSession.rollback();
    }finally{
        MybatisUtils.closeSqlSession();
    }
}

创建测试类

public class UpdateUsersTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        Users users = usersService.preUpdateUsers(1);
        users.setUsername("yanbaobao");
        users.setUsersex("female");

        usersService.modifyUsers(users);
    }
}
3、删除用户

修改配置文件

<!--根据ID删除用户-->
<delete id="deleteUsersById">
    delete from users where userid = #{userid}
</delete>

修改UsersDao接口

void deleteUsersById(int userid);

修改UsersDao接口实现类

/**
 * 根据ID删除用户
 * @param userid
 */
@Override
public void deleteUsersById(int userid) {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    int delete = sqlSession.delete("com.bjsxt.mapper.UsersMapper.deleteUsersById", userid);
}

修改UsersService接口

void dropUsersById(int userid);

修改UsersService接口实现类

/**
 * 根据ID删除用户
 * @param userid
 */
@Override
public void dropUsersById(int userid) {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    try{
        UsersDaoImpl usersDao = new UsersDaoImpl();
        usersDao.deleteUsersById(userid);
        sqlSession.commit();
    }catch(Exception e){
        e.printStackTrace();
        sqlSession.rollback();
    }finally{
        MybatisUtils.closeSqlSession();
    }
}

创建测试类

package com.bjsxt.test;

import com.bjsxt.service.impl.UsersServiceImpl;

public class DeleteUsersTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        usersService.dropUsersById(1);
    }
}

【关于上述sqlSession空指针异常的解决】

原因:创建的session对象添加到threadLocal以后并没有再获取,实际得到的仍是null。

// 获取sqlSession
public static SqlSession getSqlSession(){
    SqlSession sqlSession = threadLocal.get();
    if(sqlSession == null){
        SqlSession session = sqlSessionFactory.openSession();
        threadLocal.set(session);
    }
    return sqlSession;
}

解决:保证能获取到sqlSession对象即可

// 推荐
// 获取sqlSession
public static SqlSession getSqlSession(){
    SqlSession sqlSession = threadLocal.get();
    if(sqlSession == null){
        sqlSession = sqlSessionFactory.openSession();
        threadLocal.set(sqlSession);
    }
    return sqlSession;
}
// 获取sqlSession
public static SqlSession getSqlSession(){
    SqlSession sqlSession = threadLocal.get();
    if(sqlSession == null){
        SqlSession session = sqlSessionFactory.openSession();
        threadLocal.set(session);
    }
    return threadLocal.get();
}
// 获取sqlSession
public static SqlSession getSqlSession(){
    SqlSession sqlSession = threadLocal.get();
    if(sqlSession == null){
        SqlSession session = sqlSessionFactory.openSession();
        threadLocal.set(session);
    }
    sqlSession = threadLocal.get();
    return sqlSession;
}

Mybatis日志处理框架

常用的日志处理框架

对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪、错误排查、基于日志的业务逻辑统计分析等都离不日志。

日志管理是系统很重要的一部分,千万不可忽略其重要性。完整的日志将会在系统维护中起着异常重要的作用。

在Java领域常用的日志框架包括Log4j、Log4j2,Commons Logging、Slf4j、Logback、Jul。

1、Log4j简介

Log4j:即Log For Java (Java的日志) 是Apache提供的一个开源的Java主流的日志框架。

Log4j 的日志级别

Log4j 定义了8个日志级别,优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL。

在Log4j 中建议只使用DEBUG、INFO、WARN、ERROR四个日志级别。

  • ALL最低等级的,用于打开所有日志记录

  • TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用

  • DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息

  • INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志

  • WARN表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示

  • ERROR指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日,可以使用这个级别

  • FATAL指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了

  • OFF最高等级的,用于关闭所有日志记录

2、Log4j的使用

  1. log4j.jar包在搭建项目时已经导入。
  2. 创建配置文件 log4j.properties
log4j.rootLogger=info,console,logfile

### appender.console输出到控制台 ###
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=<%d> %5p (%F:%L) [%t] (%c) - %m%n
log4j.appender.console.Target=System.out

### appender.logfile输出到日志文件 ###
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=SysLog.log
log4j.appender.logfile.MaxFileSize=500KB
log4j.appender.logfile.MaxBackupIndex=7
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=<%d> %p (%F:%L) [%t] %c - %m%n
  1. 测试类

    package com.bjsxt.test;
    
    import org.apache.log4j.Logger;
    
    public class LogDemo {
        private final static Logger logger = Logger.getLogger(LogDemo.class);
        public static void main(String[] args) {
            String str = "bjsxt";
            // 由于日志级别设置为info高于debug,所以debug的信息不显示
            logger.debug(str);
    
            try{
                String temp = null;
                temp.length();
            }catch(Exception e){
                logger.debug(e);
                logger.error(e);
            }
        }
    }
    
  2. 可以通过在log4j.properties调整level来选择要展示的日志信息

  3. src目录下的SysLog.log文件记录了日志信息

3、commons-logging + Log4j

commons-logging是Apache的Commons项目中提供的一个高层的日志框架,是门面模式的典型应用。

commons-logging本身没有实现真正的日志能力。它可以挂接不同的日志系统,默认情况下,Commons Logging自动搜索并使用 Log4j,如果没有找到 Log4j,再使用JDK Logging。

  • log4j.jar
  • commons-logging.jar
import org. apache.commons. logging.Log;
import org.apache.commons. logging. LogFactory;
public class LogDemo {
    private static final Log logger = LogFactory.getLog(LogDemo.class) ;
package com.bjsxt.test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class CommonsLogDemo {
    private final static Log log = LogFactory.getLog(CommonsLogDemo.class);
    public static void main(String[] args) {
        log.debug("commons-logging");
        try{
            String str = null;
            str.length();
        }catch(Exception e){
            log.error("abc",e);
        }
    }
}

4、slf4j-api + slf4j-log4j + log4j

SLF4J的全称是simple Logging Facade for Java,即简单日志门面应用。SLF4J并不是具体的日志框架,而是作为一个简单门面服务于各类日志框架,如 java.util.logging,、logback和 log4j。

  • slf4j-api.jar
  • slf4j-log4j.jar
  • log4j.jar
package com.bjsxt.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Slf4jDemo {
    private final static Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);

    public static void main(String[] args) {
        logger.debug("slf4j");
        try{
            String str = null;
            str.length();
        }catch(Exception e){
            logger.error("aaa",e);
        }
    }
}

5、Mybatis的日志管理

Mybatis 的内置日志工厂(LogFactory)提供日志处理功能,内置日志工厂将日志交给以下其中一种工具作代理:

  • SLF4J
  • Apache Commons Logging
  • Log4j2
  • Log4j
  • JDK logging
  • NO_LOGGING

MyBatis内置日志工厂基于运行时自省机制选择合适的日志工具。它会使用第一个查找得到的工具 ( 按上文列举的顺序查找 )。如果一个都未找到,日志功能就会被禁用。也就是说在项目中把日志工具环境配置出来后,不用再MyBatis进行配置就可以让日志生效。

如何指定要使用的日志框架?

在mybatis-config.xml文件中添加setting标签,位置有要求,configuration标签中的各标签有顺序要求

<?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文件-->
    <properties resource="db.properties"/>
    <!--更改要使用的日志框架-->
    <settings>
        <setting name="logImpl" value="SLF4J"/>
    </settings>
    <!--环境配置-->
    <environments default="development">
        <environment id="development">
            <!--配置事务-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射配置文件-->
    <mappers>
        <!--使用相对路径引入-->
        <mapper resource="com/bjsxt/mapper/UsersMapper.xml"/>
    </mappers>
</configuration>

Mybatis的别名配置

Mybatis中的别名配置它只和 XML配置有关,只在XML文件中有效,只用来减少类完全限定名的多余部分。注
意:别名都是大小写不敏感的。在配置文件中为类的完整路径定义别名,可以采用两种方式:

方式一:使用typeAlias指定单个类的别名

忽略包含包名的完整类名,指定别名

<typeAliases>
    <typeAlias type="com.bjsxt.pojo.Users" alias="u"/>
</typeAliases>
<select id="selectUsersById" parameterType="int" resultType="u">
    select * from users where userid = #{suibian}
</select>

方式二:使用package指定某个包下所有类的默认别名

忽略包名,以类名为别名,不区分大小写

<typeAliases>
    <package name="com.bjsxt.pojo"/>
</typeAliases>
<select id="selectUsersById" parameterType="int"resultType="users" >
    select * from users where userid = #{suibian}
</select>

SqlSession对象下的常用API

SqlSession对象下的方法是用来执行定义在映射配置文件中的SELECT,INSERT,UPDATE和DELETE语句。通过SqlSession对象下的API向SQL语句传递参数时,参数可以是基本数据类型、包装类类型,POJO或 Map。但是只能有一个参数。

SqlSession对象下的方法需要 namespace + id来定位需要执行的SQL语句。namespace + id 的作用:即namespace定位到唯一的mapper映射文件, id定位到这个mapper映射文件的指定的SQL语句。

查询操作

  • selectOne

  • selectList

  • selectMap

    <!--根据用户名和性别查询-->
    <select id="selectUsersByNameAndSex" resultType="u">
        select * from users where username = #{name} and usersex = #{sex}
    </select>
    
    Map<Integer,Users> selectUsersByNameAndSex(String username,String usersex);
    
    /**
     根据用户名和性别查询
     * @param username
     * @param usersex
     * @return
     */
    @Override
    public Map<Integer, Users> selectUsersByNameAndSex(String username, String usersex) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        Map<String,String> param = new HashMap<>();
        // param中给定的key的值,必须和映射配置文件中select语句中给的占位符的名字一致
        param.put("name",username);
        param.put("sex",usersex);
        Map<Integer, Users> users = sqlSession.selectMap("com.bjsxt.mapper.UsersMapper.selectUsersByNameAndSex", param, "userid");
        return users;
    }
    
    Map<Integer,Users> findUsersByNameAndSex(String username,String usersex);
    
    /**
     * 根据用户名和性别查询
     * @param username
     * @param usersex
     * @return
     */
    @Override
    public Map<Integer, Users> findUsersByNameAndSex(String username, String usersex) {
        Map<Integer, Users> users = null;
        try{
            UsersDaoImpl usersDao = new UsersDaoImpl();
            users = usersDao.selectUsersByNameAndSex(username, usersex);
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            MybatisUtils.closeSqlSession();
        }
        return users;
    }
    
    public class SelectMapTest {
        public static void main(String[] args) {
            UsersServiceImpl usersService = new UsersServiceImpl();
            Map<Integer, Users> map = usersService.findUsersByNameAndSex("lisi", "male");
            Set<Integer> set = map.keySet();
            for(Integer key:set){
                Users user = map.get(key);
                System.out.println(key);
                System.out.println(user.getUsername()+"\t"+user.getUsersex());
            }
        }
    }
    

DML操作

  • insert
  • update
  • delete

Mapper动态代理

1、基于Mybatis的Dao层设计

在Mybatis 中对于Dao层的设计提供了两种方式:

Dao层不使用Mapper动态代理

所谓不使用动态代理是指在Dao层需要我们自己来创建Dao层的接口与接口实现类。在接口实现类的方法中我们自己通过调用SqlSession对象的方法完成数据库的操作。目前我们的入门案例就是通过这种方式完成了对users表的CRUD操作。

Dao层不使用Mapper动态代理缺点:

  • 在SqlSession对象的常用方法中只能向SQL语句中传递一个参数。如果要多个参数,需要封装到POJO或者Map 中
  • 调用SqlSession对象的方法时会有硬编码现象namespace + id

Dao层使用Mapper动态代理

在MyBatis 中提供了另外一种Dao层的实现方式,即Mapper动态代理(或称为接口绑定)的操作方式。这种方式下程序员只需要写Dao接口,不需要创建Dao的接口实现类,Mybatis会自动生成接口实现类的代理对象。在Dao层我们只要创建接口与映射配置文件即可。这种方式可以大大简化Dao层的代码结构,是在开发中最常见的使用方式。

2、Mapper动态代理规范

  • 接口名和映射配置文件名必须相同
  • 映射配置文件中namespace必须是接口全名
  • 接口中方法名和映射配置文件中表示SQL语句的标签的 id的值必须相同
  • 接口中方法返回值类型和映射配置文件中resultType指定的类型一致(单个指类型,集合指其中元素的类型)

动态代理的使用

1、搭建项目

创建项目

添加jar包

创建实体类

public class Users {
    private int userid;
    private String username;
    private String usersex;

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public String getUsersex() {
        return usersex;
    }

    public void setUsersex(String usersex) {
        this.usersex = usersex;
    }

    @Override
    public String toString() {
        return "Users{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", usersex='" + usersex + '\'' +
                '}';
    }
}

创建Mybatis工具类

public class MybatisUtils {
    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();
    private static SqlSessionFactory sqlSessionFactory = null;
    static{
        // 创建SqlSessionFactory对象
        InputStream is = null;
        try{
            is = Resources.getResourceAsStream("mybatis-config.xml");
        }catch(IOException e){
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    }

    // 获取sqlSession
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = threadLocal.get();
        if(sqlSession == null){
            sqlSession = sqlSessionFactory.openSession();
            threadLocal.set(sqlSession);
        }
        return sqlSession;
    }

    // 关闭sqlSession
    public static void closeSqlSession(){
        SqlSession sqlSession = threadLocal.get();
        if(sqlSession != null){
            sqlSession.close();
            threadLocal.set(null);
        }
    }
}

2、配置Mybatis框架

添加db.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bjsxt?useSSL=false
jdbc.username=root
jdbc.password=1615

添加log4j.properties文件

log4j.rootLogger=debug,console,logfile

### appender.console?????? ###
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=<%d> %5p (%F:%L) [%t] (%c) - %m%n
log4j.appender.console.Target=System.out

### appender.logfile??????? ###
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=SysLog.log
log4j.appender.logfile.MaxFileSize=500KB
log4j.appender.logfile.MaxBackupIndex=7
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=<%d> %p (%F:%L) [%t] %c - %m%n

添加全局配置文件

<?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文件-->
    <properties resource="db.properties"/>
    <!--更改要使用的日志框架-->
    <settings>
        <!--默认是LOG4J-->
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!--给类配置别名-->
    <typeAliases>
        <typeAlias type="com.bjsxt.pojo.Users" alias="u"/>
        <package name="com.bjsxt.pojo"/>
    </typeAliases>
    <!--环境配置-->
    <environments default="development">
        <environment id="development">
            <!--配置事务-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射配置文件-->
    <mappers>
        <!--通过指定包名或接口名的方式-->
        <package name="com.bjsxt.mapper"/>
    </mappers>
</configuration>

添加UsersMapper接口

public interface UsersMapper {
}

添加UsersMapper映射配置文件

<?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.bjsxt.mapper.UsersMapper">

</mapper>

3、实现查询所有用户

修改映射配置文件

<?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.bjsxt.mapper.UsersMapper">
    <!--查询所有用户-->
    <select id="selectUsersAll" resultType="Users">
        select * from users
    </select>
</mapper>

修改UsersMapper接口添加抽象方法

List<Users> selectUsersAll();

创建业务层接口

List<Users> findUsersAll();

创建业务层接口实现类

public class UsersServiceImpl implements UsersService {

    /**
     * 查询所有用户
     * @return
     */
    @Override
    public List<Users> findUsersAll() {
        List<Users> list = null;
        try{
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            // 根据给定接口,获取对应的接口实现类代理对象
            UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
            list = mapper.selectUsersAll();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            MybatisUtils.closeSqlSession();
        }
        return list;
    }
}

创建测试类

public class SelectUsersAllTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        List<Users> list = usersService.findUsersAll();
        // list.forEach(e -> System.out.println(e));
        list.forEach(System.out::println);
    }
}

4、实现根据ID查询用户

修改映射配置文件

<!--根据ID查询用户-->
<select id="selectUsersById" resultType="Users">
    select * from users where userid = #{userid}
</select>

修改UsersMapper接口添加抽象方法

Users selectUsersById(int userid);

修改业务层接口添加方法

Users findUsersById(int userid);

修改业务层接口实现类添加方法

/**
 * 根据ID查询用户
 * @param userid
 * @return
 */
@Override
public Users findUsersById(int userid) {
    Users user = null;
    try{
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        user = mapper.selectUsersById(userid);
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        MybatisUtils.closeSqlSession();
    }
    return user;
}

创建测试类

public class SelectUsersByIdTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        Users user = usersService.findUsersById(1);
        System.out.println(user);
    }
}

5、动态代理的多参数处理

顺序传参法

在映射文件中,SQL语句中的参数需要使用arg0,arg1…或者param1,param2…表示参数的顺序。此方法可读性低,且要求参数的顺序不能出错,在开发中不建议使用。

修改映射配置文件

<!--根据姓名和性别查询,使用顺序传参法-->
<!--【注意】必须指定resultType-->
<select id="selectUsersOrderParam" resultType="Users">
    select * from users where username = #{param1} and usersex = #{param2}
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersOrderParam(String username,String usersex);

创建测试类

public class SelectUsersOrderParamTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        List<Users> list = mapper.selectUsersOrderParam("huangzi","female");
        list.forEach(System.out::println);
    }
}
@param注解传参法

在接口方法的参数列表中通过@Param注解来定义参数名称,在SQL语句中通过注解中所定义的参数名称完成参数位置的指定。此方式在参数不多的情况还是比较直观的,推荐使用。

修改映射配置文件

<!--根据姓名和性别查询,使用@Param传参法-->
<select id="selectUsersAnnParam" resultType="Users">
    select * from users where username = #{name} and usersex = #{sex}
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersAnnParam(@Param("name") String username,@Param("sex") String usersex);

创建测试类

public class SelectUsersAnnParamTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        List<Users> list = mapper.selectUsersAnnParam("huangzi","female");
        list.forEach(System.out::println);
    }
}
POJO传参法

在SQL语句中绑定参数时使用POJO的属性名作为参数名即可。此方式推荐使用。

修改映射配置文件

<!--根据姓名和性别查询,使用POJO传参法-->
<select id="selectUsersPojoParam" resultType="Users">
    select * from users where username = #{username} and usersex = #{usersex}
</select>

修改UsersMapper接口添加方法

// 【注意】传入参数为Users对象
List<Users> selectUsersPojoParam(Users users);

创建测试类

public class SelectUsersPojoParamTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        Users user = new Users();
        user.setUsername("huangzi");
        user.setUsersex("female");
        List<Users> list = mapper.selectUsersPojoParam(user);
        list.forEach(System.out::println);
    }
}
Map传参法

在SQL语句中绑定参数时使用Map的 Key作为参数名即可。此方法适合在传递多参数时,如果没有POJO能与参数匹配,可以使用该方式传递参数。推荐使用。MyBatis传递map参数时,如果传递参数中没有对应的 key值,在执行SQL语句时默认取的是null。

修改映射配置文件

<!--根据姓名和性别查询,使用Map传参法-->
<select id="selectUsersMapParam" resultType="Users">
    select * from users where username = #{key_name} and usersex = #{key_sex}
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersMapParam(Map<String,String> map);

创建测试类

public class SelectUsersMapParamTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        Map<String,String> map = new HashMap<>();
        // 【注意】传入map的key的值,必须和映射配置文件中给的占位符完全一致
        map.put("key_name","huangzi");
        map.put("key_sex","female");

        List<Users> list = mapper.selectUsersMapParam(map);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

6、映射配置文件中的特殊字符处理

在Mybatis的映射配置文件中不可以使用一些特殊字符;经测试,新版本已解决该问题。

对于旧版本,有两种方式可解决该问题:

使用符号实体

image-20211027094327804
<!--查询ID大于16的用户-->
<select id="selectUsers" resultType="Users">
    select * from users where userid &gt; #{userid}
</select>

使用CDATA区

CDATA:全称为Character Data,以"<![CDATA["内容"]]>",CDATA中的内容不会被解析程序解析。

<!--查询ID大于16的用户-->
<select id="selectUsers" resultType="Users">
    select * from users where userid <![CDATA[>]]> #{userid}
</select>

7、Mybatis分页

1、使用RowBounds分页

RowBounds是 Mybatis提供的一个专门处理分页的对象。在RowBounds对象中有两个成员变量:

offset:偏移量,从О开始计数,从哪一行开始
limit:限制条数

使用RowBounds进行分页,非常方便,不需要在SQL语句中写limit,即可完成分页功能。但是由于它是在SQL查询出所有结果的基础上截取数据的,所以在数据量大的SQL中并不适用,它更适合在返回数据结果较少的查询中使用。

修改映射配置文件

<!--查询所有用户,使用RowBounds分页-->
<select id="selectUsersRowBounds" resultType="Users">
    select * from users
</select>

修改UsersMapper接口添加方法

// 【注意】传入参数是RowBounds对象
List<Users> selectUsersRowBounds(RowBounds rowBounds);

创建测试类

public class SelectUsersRowBoundsTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        // 从第一行数据开始,返回五行数据
        RowBounds rowBounds = new RowBounds(0,5);
        List<Users> list = mapper.selectUsersRowBounds(rowBounds);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}
2、使用SQL语句分页

在分页查询时,如果返回的结果较多,那么需要使用特定的SQL语句来实现分页处理。在MySQL数据库中我们可以使用 limit关键字实现分页。

修改映射配置文件

<!--查询所有用户,使用limit分页-->
<select id="selectUsersLimit" resultType="Users">
    select * from users limit #{offset},#{limit}
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersLimit(@Param("offset") int offset,@Param("limit") int limit);

创建测试类

public class SelectUsersLimitTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        // 从第一行数据开始,返回五行数据
        List<Users> list = mapper.selectUsersLimit(0,5);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

8、Mapper动态代理模式下的DML操作

实现添加用户业务

修改映射配置文件

<!--添加用户-->
<insert id="insertUsers">
    insert into users values(default,#{username},#{usersex})
</insert>

修改UsersMapper接口

void insertUsers(Users user);

创建测试类

public class InsertUsersTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Users users = new Users();
        users.setUsername("xitu");
        users.setUsersex("robot");
        mapper.insertUsers(users);
        // 【注意】手动提交事务,负责不能添加成功
        sqlSession.commit();
        MybatisUtils.closeSqlSession();
    }
}

9、主键值回填

在数据库中插入数据时,有时我们是需要获取新数据的主键值。在Mybatis中支持主键值回填。

Mybatis 中支持两种方法获取主键:

  • 获取自增主键的值。如:MySQL、sqlServer
  • 获取非自增主键的值。如Oracle
1、获取自增主键值——开启自动获取主键值

局部配置

在映射配置文件的insert标签中,指定属性和对应的值

<!--添加用户,获取主键值[自增]-->
<!--【注意】useGeneratedKeys keyProperty-->
<insert id="insertUsersGetKey" useGeneratedKeys="true" keyProperty="userid">
    insert into users values(default,#{username},#{usersex})
</insert>

全局配置

在mybatis-config.xml文件的settings标签中,添加一个setting标签

<!--【注意】如果使用全局配置,应保留局部配置中的keyProperty="userid",才能返回主键值-->
<settings>
    <!--默认是LOG4J-->
    <setting name="logImpl" value="LOG4J"/>
    <setting name="useGeneratedKeys" value="true"/>
</settings>
2、获取非自增主键

修改映射配置文件

<!--添加用户,获取主键值[非自增] 【oracle】在插入数据之前先获取序列值-->
<insert id="insertUsersGetKey2">
    <selectKey order="BEFORE" keyProperty="userid" resultType="int">
        select seq.nextval from dual
    </selectKey>
    insert into users values(userid,#{username},#{usersex})
</insert>

<!--添加用户,获取主键值[非自增] 【mysql】插入数据之后再获取主键值-->
<!--在【mysql】中select last_insert_id()和select @@identity作用一样-->
<insert id="insertUsersGetKey3">
    <selectKey order="AFTER" keyProperty="userid" resultType="int">
        select last_insert_id()
    </selectKey>
    insert into users values(default,#{username},#{usersex})
</insert>

动态SQL

MyBatis 提供了动态SQL功能。在XML映射文件中使用标签拼接SQL语句。
MyBatis中动态SQL是编写在mapper.xml中的,其语法和JSTL类似,但是却是基于强大的OGNL表达式实现的。

1、if 单分支判断语句

修改映射配置文件

<!--根据给定条件查询用户-->
<select id="selectUsersByProperty" resultType="Users">
    select * from users where 1=1
        /*test中的值根据传参方法确定*/
        <if test="userid != 0">
            and userid = #{userid}
        </if>
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="usersex != null and usersex != ''">
            and usersex = #{usersex}
        </if>
</select>

修改UsersMapper接口

List<Users> selectUsersByProperty(Users users);

修改业务层接口添加方法

List<Users> findUsersByProperty(Users users);

修改业务层接口实现类添加方法

/**
 * 根据给定条件查询 动态SQL
 * @param users
 * @return
 */
@Override
public List<Users> findUsersByProperty(Users users) {
    List<Users> list = null;
    try{
        SqlSession sqlSession  = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        list = mapper.selectUsersByProperty(users);
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        MybatisUtils.closeSqlSession();
    }
    return list;
}

创建测试类

public class FindUsersByPropertyTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        Users users = new Users();
        users.setUserid(20);
        users.setUsername("xitu");
        users.setUsersex("robot");
        List<Users> list = usersService.findUsersByProperty(users);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

【正常运行,日志中的SQL语句也会显示注释内容】
在这里插入图片描述

2、choose when otherwise标签

多选一,优先拼接满足条件的分支到SQL语句中。

修改映射配置文件

<!--多选一条件-->
<select id="selectUsersByChoose" resultType="Users">
    select * from users where 1=1
    <choose>
        <when test="username != null and username != ''">
            and username = #{username}
        </when>
        <when test="usersex != null and usersex != ''">
            and usersex = #{usersex}
        </when>
        <otherwise>
            and userid = 1
        </otherwise>
    </choose>
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersByChoose(Users users);

修改UsersService接口添加方法

List<Users> findUsersByChoose(Users users);

修改UsersService接口实现类添加方法

/**
 * choose多分支查询
 * @param users
 * @return
 */
@Override
public List<Users> findUsersByChoose(Users users) {
    List<Users> list = null;
    try{
        SqlSession sqlSession  = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        list = mapper.selectUsersByChoose(users);
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        MybatisUtils.closeSqlSession();
    }
    return list;
}

创建测试类

public class FindUsersByChooseTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        Users users = new Users();
        users.setUsername("xitu");
        List<Users> list = usersService.findUsersByChoose(users);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

3、where标签

使用where标签,就不需要提供where 1=1这样的条件了。如果判断条件不为空则自动添加where关键字,并且会自动去掉第一个条件前面的and或 or。

修改映射配置文件

<!--where标签-->
<select id="selectUsersByPropertyWhere" resultType="Users">
    select * from users
    <where>
        <if test="userid != 0">
            and userid = #{userid}
        </if>
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="usersex != null and usersex != ''">
            and usersex = #{usersex}
        </if>
    </where>
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersByPropertyWhere(Users users)

修改UsersService接口添加方法

List<Users> findUsersByPropertyWhere(Users users);

修改UsersService接口实现类添加方法

/**
 * where标签查询
 * @param users
 * @return
 */
@Override
public List<Users> findUsersByPropertyWhere(Users users) {
    List<Users> list = null;
    try{
        SqlSession sqlSession  = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        list = mapper.selectUsersByPropertyWhere(users);
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        MybatisUtils.closeSqlSession();
    }
    return list;
}

创建测试类

public class FindUsersByPropertyWhereTest {
    public static void main(String[] args) {
        UsersServiceImpl usersService = new UsersServiceImpl();
        Users users = new Users();
        users.setUsername("xitu");
        List<Users> list = usersService.findUsersByPropertyWhere(users);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

4、bind标签

bind标签允许我们在OGNL表达式以外创建一个变量,并可以将其绑定到当前的SQL语句中。一般应用于模糊查询,通过bind绑定通配符和查询值。

修改映射配置文件

<!--不适用bind标签,可达到同样效果-->
<!--根据姓名模糊查询-->
<select id="selectUsersByLikeName" resultType="Users">
    select * from users where username like concat('%',#{name},'%')
</select>

<!--使用bind标签-->
<!--根据姓名模糊查询-->
<select id="selectUsersByLikeName1" resultType="Users">
    <bind name="likeName" value="'%'+name+'%'"/>
    select * from users where username like #{likeName}
</select>

创建测试类

public class SelectUsersByLikeNameTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        List<Users> list = mapper.selectUsersByLikeName1("xi");
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

5、set标签

set标签用在update语句中。借助if标签,可以只对有具体值的字段进行更新。set标签会自动添加set关键字,自动去掉最后一个if语句的多余的逗号。

修改映射配置文件

<!--选择更新-->
<update id="usersUpdate">
    update users
    <set>
        <if test="username != null and username != ''">
            username = #{username},
        </if>
    </set>
    <set>
        <if test="usersex != null and usersex != ''">
            usersex = #{usersex},
        </if>
    </set>
    where userid = #{userid}
</update>

创建测试类

public class UsersUpdateTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        Users users = new Users();
        users.setUserid(22);
        users.setUsername("xi_tu11");
        // 将id为22的users对象的username属性更新为xi_tu11
        mapper.usersUpdate(users);

        // 注意手动提交事务
        sqlSession.commit();
        MybatisUtils.closeSqlSession();
    }
}

6、forEach 迭代List、Set

foreach标签的功能非常强大,我们可以将任何可迭代对象如List、Set .Map或者数组对象作为集合参数传递给foreach标签进行遍历。它也允许我们指定开头与结尾的字符串以及集合项迭代之间的分隔符。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TKzZfm90-1635495309507)(C:\Users\86150\AppData\Roaming\Typora\typora-user-images\image-20211027153716454.png)]

类型:

collection="collection"

名称:

collection="suibian"

void example(@Param("suibian") List list)
void example(@Param("suibian") Set set)

修改映射配置文件

<!--查询ID为20或21的用户-->
<select id="selectUsersByIdUseCollection" resultType="Users">
    select * from users where userid in
    <foreach collection="collection" item="userid" open="(" separator="," close=")">
        #{userid}
    </foreach>
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersByIdUseCollection(List<Integer> list);

创建测试类

    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        List<Integer> list = new ArrayList<>();
        list.add(20);
        list.add(23);
        list.add(27);
        List<Users> list1 = mapper.selectUsersByIdUseCollection(list);
        list1.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

7、forEach 迭代数组

类型:

collection="array"

名称:

collection="suibian"

void example(@Param("suibian") int[] arr)

修改映射配置文件

<!--查询ID为20或21的用户-->
<select id="selectUsersByIdUseArray" resultType="Users">
    select * from users where userid in
    <foreach collection="suibian" item="userid" open="(" separator="," close=")">
        #{userid}
    </foreach>
</select>

修改UsersMapper接口添加方法

List<Users> selectUsersByIdUseArray(@Param("suibian") int[] arr);

创建测试类

public class SelectUsersByIdUseArrayTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        int[] arr = {20,23,27};
        // 传入的集合中的元素是Integer类型
        List<Users> list = mapper.selectUsersByIdUseArray(arr);
        list.forEach(System.out::println);
        MybatisUtils.closeSqlSession();
    }
}

8、forEach 迭代Map

名称:

collection="suibian" 或者 collection="suibian.entrySet()"

void example(@Param("suibian") Map map)

修改映射配置文件

<!--根据给定条件做计数处理-->
<!--计数,返回值类型为int-->
<!--字段名用$包裹-->
<!--select count(*) from users where username = "xi_tu" and usersex = "robot"-->
<select id="selectUsersCount" resultType="int">
    select count(*) from users where
    <foreach collection="suibian" item="value" index="key" separator="and">
        ${key} = #{value}
    </foreach>
</select>

修改UsersMapper接口添加方法

int selectUsersCount(@Param("suibian") Map<String,String> map);

创建测试类

public class SelectUsersCountTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Map<String, String> map = new HashMap<>();
        map.put("username","xi_tu");
        map.put("usersex","robot");
        int temp = mapper.selectUsersCount(map);
        System.out.println(temp);
        MybatisUtils.closeSqlSession();
    }
}

9、forEach 批量添加

修改映射配置文件

<!--批量添加用户-->
<insert id="insertUsersBatch">
    insert into users values
    <foreach collection="collection" item="user" separator=",">
        (default,#{user.username},#{user.usersex})
    </foreach>
</insert>

修改UsersMapper接口添加方法

void insertUsersBatch(List<Users> users);

创建测试类

public class InsertUsersBatchTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        List<Users> list = new ArrayList<>();
        Users user = null;
        for (int i = 0; i < 10; i++) {
            user = new Users();
            user.setUsername("test"+i);
            user.setUsersex("robot"+i);
            list.add(user);
        }
        mapper.insertUsersBatch(list);
        // 手动提交事务
        sqlSession.commit();
        MybatisUtils.closeSqlSession();
    }
}

Mybatis缓存

缓存是一般的ORM框架都会提供的功能,目的是提升查询的效率和减少数据库的压力,缓存的重要性是不言而喻的。Mybatis会将相同查询条件的SQL语句的查询结果存储在内存或者某种缓存介质当中,当下次遇到相同的查询SQL时不再执行该SQL,而是直接从缓存中获取结果,减少服务器的压力,尤其是在查询越多、缓存命中率越高的情况下,使用缓存对性能的提高更明显。

MyBatis 缓存方式分为一级缓存和二级缓存,同时也可配置关于缓存设置。

一级缓存是将结果缓存在SqlSession对象中,二级缓存是存储在SqlSessionFactory对象中。默认情况下,MyBatis开启一级缓存,没有开启二级缓存。当数据量大的时候可以借助一些第三方缓存技术来协助保存 Mybatis的二级缓存数据。

1、一级缓存

一级缓存也叫本地缓存,MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis的一级缓存是默认开启的,不需要任何的配置。

一级缓存的生命周期

MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

  • 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是SqlSession对象仍可使用
  • SqlSession中执行了任何一个增删改的update操作,都会清空PerpetualCache对象的数据,但是SqlSession对象可以继续使用

如何判断两次查询是完全相同的查询

Mybatis 认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

  • 传入的statementld
  • 查询时要求的结果集中的结果范围
  • 这次查询所产生的最终要传递给Preparedstatement的SQL语句字符串
  • 传递的字符串

2、二级缓存

MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。二级缓存是sqlSessionFactory 上的缓存,可以由一个SqlSessionFactory创建的不同的SqlSession之间共享缓存数据。默认并不开启。sqlSession在执行commit()或者close()的时候将数据放入到二级缓存。

二级缓存的配置方式

在某个具体的映射配置文件中,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求缓存的POJO类必须是可序列化的,也就是要求实现Serializable接口。在映射配置文件中配置<cache/>就可以开启缓存了。

在全局配置文件中,二级缓存是默认开启的;但是对于某个具体的映射配置文件,二级缓存默认不开启。

1、配置cacheEnabled

在mybatis-config.xml文件的settings标签中配置开启二级缓存,cacheEnabled的默认值就是true,此步骤可以省略。

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
2、在映射配置文件中添加<cache/>
<mapper namespace="com.bjsxt.mapper.UsersMapper">
    <cache/>
</mapper>

二级缓存特点

  • 映射语句文件中的所有select语句将会被缓存
  • 映射语句文件中的所有insert、update和delete语句会刷新缓存
  • 二级缓存是以namespace为单位的,不同namespace下的操作互不影响
  • 如果在加入<cache/>标签的前提下让个别select标签不使用缓存,可以使用useCache属性,设置为false
  • 缓存会使用默认的Least Recently Used (LRU,最近最少使用的)算法来收回
  • 根据时间表,比如No Flush Interval,(CNFI 没有刷新间隔),缓存不会以任何时间顺序来刷新
  • 缓存会存储列表集合或对象 (无论查询方法返回什么) 的1024个引用
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改

Mybatis多表关联查询

1、搭建环境

创建项目

添加jar包

创建实体

public class Users {
    private int userid;
    private String username;
    private String usersex;

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public String getUsersex() {
        return usersex;
    }

    public void setUsersex(String usersex) {
        this.usersex = usersex;
    }

    @Override
    public String toString() {
        return "Users{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", usersex='" + usersex + '\'' +
                '}';
    }
}

创建db.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bjsxt?useSSL=false
jdbc.username=root
jdbc.password=1615

创建log4j.properties文件

log4j.rootLogger=debug,console,logfile

### appender.console?????? ###
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=<%d> %5p (%F:%L) [%t] (%c) - %m%n
log4j.appender.console.Target=System.out

### appender.logfile??????? ###
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=SysLog.log
log4j.appender.logfile.MaxFileSize=500KB
log4j.appender.logfile.MaxBackupIndex=7
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=<%d> %p (%F:%L) [%t] %c - %m%n

添加工具类

public class MybatisUtils {
    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();
    private static SqlSessionFactory sqlSessionFactory = null;
    static{
        // 创建SqlSessionFactory对象
        InputStream is = null;
        try{
            is = Resources.getResourceAsStream("mybatis-config.xml");
        }catch(IOException e){
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    }

    // 获取sqlSession
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = threadLocal.get();
        if(sqlSession == null){
            sqlSession = sqlSessionFactory.openSession();
            threadLocal.set(sqlSession);
        }
        return sqlSession;
    }

    // 关闭sqlSession
    public static void closeSqlSession(){
        SqlSession sqlSession = threadLocal.get();
        if(sqlSession != null){
            sqlSession.close();
            threadLocal.set(null);
        }
    }
}

创建全局配置文件

<?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文件-->
    <properties resource="db.properties"/>
    <!--更改要使用的日志框架-->
    <settings>
        <!--默认是LOG4J-->
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!--给类配置别名-->
    <typeAliases>
        <typeAlias type="com.bjsxt.pojo.Users" alias="u"/>
        <package name="com.bjsxt.pojo"/>
    </typeAliases>
    <!--环境配置-->
    <environments default="development">
        <environment id="development">
            <!--配置事务-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射配置文件-->
    <mappers>
        <!--通过指定包名或接口名的方式-->
        <package name="com.bjsxt.mapper"/>
    </mappers>
</configuration>

创建映射配置文件

<?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.bjsxt.mapper.UsersMapper">

</mapper>

2、手动处理映射关系

resultMap标签是Mybatis最强大的元素,它可以将查询到的复杂数据(比如查询到几个表中数据)映射到一个结果集当中。

2.1 resultMap的基础使用场景

在Mybatis 中如果查询的结果集的列名与POJO的属性名相同,那么我们是不需要在Mybatis 中配置映射关系的,但是当查询到的结果集的列名与POJO的属性名不匹配时,Mybatis是无法完成影射处理的。

解决方案:

  • 通过定义列别名的方式来解决该问题
  • 通过在resultMap标签中定义映射关系来解决该问题
2.2 通过resultMap标签解决实体与结果集的映射

修改映射配置文件

<?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.bjsxt.mapper.UsersMapper">
    <!--type的值,如果POJO类起了别名,可以用别名-->
    <resultMap id="usersMapper" type="com.bjsxt.pojo.Users">
        <!--property POJO类-->
        <!--column 结果集-->
        <id property="userid" column="id"/>
        <result property="username" column="name"/>
        <result property="usersex" column="sex"/>
    </resultMap>
    <!--手动指定映射关系后,在select标签中使用resultMap-->
    <select id="selectUsersAll" resultMap="usersMapper">
        select userid as id,username as name,usersex as sex from users
    </select>
</mapper>

创建测试类

public class SelectUsersAllTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        List<Users> list = mapper.selectUsersAll();

        /**
         * 输出结果23个null,原因:结果集列名和映射配置文件中指定的返回类型不一致,无法完成映射
         */
        /**
         * 修改映射配置文件后,完成映射,可正常输出
         */
        list.forEach(System.out::println);
    }
}

3、一对一的关联查询

3.1 <association>标签

<association>标签是处理单一的关联对象(处理单一属性的关联关系)。

  • property:指定关联对象的属性
  • javaType:关联对象的类型(可以省略)
  • select:执行一个新的查询
  • column:在新的查询中用哪个列的值作为查询条件
3.2 需求

完成用户与角色查询。要求一个用户只能对应一个角色。

3.3 实现

创建roles表

CREATE TABLE `roles` (
    `roleid` int(11) NOT NULL,
    `rolename` varchar(30) DEFAULT NULL,
    `user_id` int(11) DEFAULT NULL,
    PRIMARY KEY (`roleid`),
    UNIQUE KEY `role_fk` (`user_id`) USING BTREE,
    CONSTRAINT `role_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建Roles实体

public class Roles {
    private int roleid;
    private String rolename;

    public int getRoleid() {
        return roleid;
    }

    public void setRoleid(int roleid) {
        this.roleid = roleid;
    }

    public String getRolename() {
        return rolename;
    }

    public void setRolename(String rolename) {
        this.rolename = rolename;
    }

    @Override
    public String toString() {
        return "Roles{" +
                "roleid=" + roleid +
                ", rolename='" + rolename + '\'' +
                '}';
    }
}

修改Users实体

public class Users {
    private int userid;
    private String username;
    private String usersex;
    // 添加Roles类型的属性roles
    private Roles roles;

    public Roles getRoles() {
        return roles;
    }

    public void setRoles(Roles roles) {
        this.roles = roles;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public String getUsersex() {
        return usersex;
    }

    public void setUsersex(String usersex) {
        this.usersex = usersex;
    }

    @Override
    public String toString() {
        return "Users{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", usersex='" + usersex + '\'' +
                '}';
    }
}

修改映射配置文件

<!--一对一关联查询映射-->
<resultMap id="usersAndRolesMapper" type="Users">
    <id property="userid" column="userid"/>
    <result property="username" column="username"/>
    <result property="usersex" column="usersex"/>
    <!--javaType为可选属性,可不写-->
    <!--两张表的映射关系都需要指定-->
    <association property="roles" javaType="com.bjsxt.pojo.Roles">
        <id property="roleid" column="roleid"/>
        <result property="rolename" column="rolename"/>
    </association>
</resultMap>
<!--如果使用resultMap却不明确指定映射关系,即使列名和属性对应也会忽略-->
<!--所以只要使用resultMap,就必须明确指定映射关系-->
<select id="selectUsersAnsRolesById" resultMap="usersAndRolesMapper">
    select * from users as u,roles as r where u.userid = r.user_id and u.userid = #{userid}
</select>

创建测试类

public class SelectUsersAnsRolesByIdTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Users users = mapper.selectUsersAnsRolesById(1);
        System.out.println(users+users.getRoles().getRolename());
    }
}

4、一对多关联查询

4.1 <collection>标签

<collection>标签是处理所关联对象是多个的(处理关联属性是集合时的关联关系)。

  • property:指定关联对象的属性
  • javaType:关联对象的类型(可以省略。默认为List类型,如果集合是Set类型时需要配置并给定set 的全名)
  • ofType:指定集合里存放的对象类型
  • select:执行一个新的查询
  • column:在新的查询中用哪个列的值作为查询条件
4.2 需求

完成用户与订单查询,要求一个用户可以对应多个订单。

4.3 实现

创建Orders表

CREATE TABLE `orders` (
  `orderid` int(11) NOT NULL,
  `orderprice` double DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`orderid`),
  KEY `orders_fk` (`user_id`),
  CONSTRAINT `orders_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建Orders实体

public class Orders {
    private int orderid;
    private double orderprice;

    public int getOrderid() {
        return orderid;
    }

    public void setOrderid(int orderid) {
        this.orderid = orderid;
    }

    public double getOrderprice() {
        return orderprice;
    }

    public void setOrderprice(double orderprice) {
        this.orderprice = orderprice;
    }

    @Override
    public String toString() {
        return "Orders{" +
                "orderid=" + orderid +
                ", orderprice=" + orderprice +
                '}';
    }
}

修改Users实体

public class Users {
    private int userid;
    private String username;
    private String usersex;
    private Roles roles;
    private List<Orders> orders;

    public List<Orders> getOrders() {
        return orders;
    }

    public void setOrders(List<Orders> orders) {
        this.orders = orders;
    }

    public Roles getRoles() {
        return roles;
    }

    public void setRoles(Roles roles) {
        this.roles = roles;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public String getUsersex() {
        return usersex;
    }

    public void setUsersex(String usersex) {
        this.usersex = usersex;
    }

    @Override
    public String toString() {
        return "Users{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", usersex='" + usersex + '\'' +
                '}';
    }
}

修改映射配置文件

<!--一对多关联查询-->
<!--根据ID查询用户及其所有订单-->
<resultMap id="usersAndOrdersMapper" type="Users">
    <id property="userid" column="userid"/>
    <result property="username" column="username"/>
    <result property="usersex" column="usersex"/>
    <collection property="orders" javaType="List" ofType="com.bjsxt.pojo.Orders">
        <id property="orderid" column="orderid"/>
        <result property="orderprice" column="orderprice"/>
    </collection>
</resultMap>
<select id="selectUsersAndOrdersById" resultMap="usersAndOrdersMapper">
    select * from users as u,orders as o where u.userid = o.user_id and userid = #{userid}
</select>

创建测试类

public class UsersAndOrdersMapperTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Users user = mapper.selectUsersAndOrdersById(1);
        System.out.println(user);
        List<Orders> orders = user.getOrders();
        for(Orders o:orders){
            System.out.println(o);
        }
    }
}

5、多对多关联查询

5.1 需求

根据ID查询用户以及订单中所包含的所有商品。

5.2 实现

创建items表

CREATE TABLE `items` (
  `itemid` int(11) NOT NULL AUTO_INCREMENT,
  `itemname` varchar(255) DEFAULT NULL,
  `itemprice` double DEFAULT NULL,
  PRIMARY KEY (`itemid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建orders_items中间表

CREATE TABLE `orders_items` (
  `order_id` int(11) NOT NULL,
  `item_id` int(11) NOT NULL,
  PRIMARY KEY (`order_id`,`item_id`),
  KEY `orders_items_fk2` (`item_id`),
  CONSTRAINT `orders_items_fk1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`orderid`),
  CONSTRAINT `orders_items_fk2` FOREIGN KEY (`item_id`) REFERENCES `items` (`itemid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建Items实体

public class Items {
    private int itemid;
    private String itemname;
    private double itemprice;

    @Override
    public String toString() {
        return "Items{" +
                "itemid=" + itemid +
                ", itemname='" + itemname + '\'' +
                ", itemprice=" + itemprice +
                '}';
    }

    public int getItemid() {
        return itemid;
    }

    public void setItemid(int itemid) {
        this.itemid = itemid;
    }

    public String getItemname() {
        return itemname;
    }

    public void setItemname(String itemname) {
        this.itemname = itemname;
    }

    public double getItemprice() {
        return itemprice;
    }

    public void setItemprice(double itemprice) {
        this.itemprice = itemprice;
    }
}

修改Orders实体

public class Orders {
    private int orderid;
    private double orderprice;
    private List<Items> items;

    public List<Items> getItems() {
        return items;
    }

    public void setItems(List<Items> items) {
        this.items = items;
    }

    public int getOrderid() {
        return orderid;
    }

    public void setOrderid(int orderid) {
        this.orderid = orderid;
    }

    public double getOrderprice() {
        return orderprice;
    }

    public void setOrderprice(double orderprice) {
        this.orderprice = orderprice;
    }

    @Override
    public String toString() {
        return "Orders{" +
                "orderid=" + orderid +
                ", orderprice=" + orderprice +
                '}';
    }
}

修改映射配置文件

<!--多对多关联查询-->
<!--根据用户ID查询用户、订单及其包含的商品-->
<resultMap id="usersAndOrdersAndItemsMapper" type="Users">
    <id property="userid" column="userid"/>
    <result property="username" column="username"/>
    <result property="usersex" column="usersex"/>
    <collection property="orders" javaType="List" ofType="com.bjsxt.pojo.Orders">
        <id property="orderid" column="orderid"/>
        <result property="orderprice" column="orderprice"/>
        <collection property="items" javaType="List" ofType="com.bjsxt.pojo.Items">
            <id property="itemid" column="itemid"/>
            <result property="itemid" column="itemid"/>
            <result property="itemname" column="itemname"/>
            <result property="itemprice" column="itemprice"/>
        </collection>
    </collection>
</resultMap>
<select id="selectUsersAndOrdersAndItemsById" resultMap="usersAndOrdersAndItemsMapper">
    select * from users as u,orders as o,orders_items as oi,items as i where u.userid = o.user_id and o.orderid = oi.order_id and oi.item_id = i.itemid and userid = #{userid}
</select>

创建测试类

public class SelectUsersAndOrdersAndItemsByIdTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Users user = mapper.selectUsersAndOrdersAndItemsById(1);
        System.out.println(user);

        List<Orders> ordersList = user.getOrders();
        for(Orders o:ordersList){
            System.out.println(o);
            List<Items> itemsList = o.getItems();
            itemsList.forEach(System.out::println);
        }
    }
}

总结

resultMap标签的使用非常灵活,collection标签可以嵌套使用。如果从订单查商品,在Orders类中创建一个List<Items> items属性;如果从商品查订单,在Items类中创建一个List<Orders> orders属性;甚至可以直接在Users类中创建一个List<Items> items属性。对于SQL语句select * from,中间表orders_items的查询是不必要的,因为其中的信息已经包含在orders、items两张表查询到的结果中了。

6、多表查询中的数据加载方式

6.1 多表查询SQL语句的写法
6.1.1 连接查询

使用内连接或者外连接的方式查询数据

  • 优点:在一次查询中完成数据的查询操作。降低查询次数提高查询效率
  • 缺点:如果查询返回的结果集较多会消耗内存空间
6.1.2 N+1次查询

分解式查询,将查询分解成多个SQL语句

  • 优点:配和着延迟加载可实现结果集分步获取,节省内存空间
  • 缺点:由于需要执行多次查询,相比连接查询效率低
6.2 N+1查询的加载方式
6.2.1 立即加载

在一次查询中执行所有的SQL语句(既不能解决内存占用,也不能提升效率)

6.2.2 延迟加载

在一次查询中执行部分SQL语句,根据操作映射的关联象触发其他查询

6.3 MyBatis的延迟加载的使用

在Mybatis 中可以使用延迟加载数据的策略实现对关联对象的查询。

使用延迟加载要求:

  • 查询方式需要使用N+1次查询
  • 在映射配置文件或者全局配置文件中开启延迟加载
6.3.1 开启延迟加载

开启延迟加载方式:

  • 在association、collection标签中通过fetchType属性开启延迟加载,仅针对当前标签生效。fetchType=“lazy"开启延迟加载,fetchType=”"eager"开启立即加载。通过select属性指定需要执行的查询的ID。通过column属性指定将封装查询结果对象的哪个属性的值作为下一个查询的查询条件。

    <!--延迟加载 N+1查询-->
    <!--根据ID查询用户-->
    <resultMap id="usersAndOrdersMapperLazy" type="Users">
        <id property="userid" column="userid"/>
        <result property="username" column="username"/>
        <result property="usersex" column="usersex"/>
        <collection property="orders" ofType="com.bjsxt.pojo.Orders" fetchType="lazy" select="selectOrdersByUserIdLazy" column="userid">
        </collection>
    </resultMap>
    <select id="selectUsersByIdLazy" resultMap="usersAndOrdersMapperLazy">
        select * from users where userid = #{userid}
    </select>
    <!--根据ID查询订单-->
    <select id="selectOrdersByUserIdLazy" resultType="com.bjsxt.pojo.Orders">
        select * from orders where user_id = #{userid}
    </select>
    
    <!--开启延迟加载以后,如未指定触发加载方法,直到user调用getOrders()方法,第二次查询才会执行-->
    
  • 在全局配置文件中的settings标签中开启延迟加载,所有的association和collection元素都生效。

    <!--开启全局延迟加载,对所有不同的映射配置文件中的所有方法都使用延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    
    <!--配置触发加载方法-->
    <!--value值为空时,user.getOrder()会触发延迟查询的加载-->
    <setting name="lazyLoadTriggerMethods" value=""/>
    <!--value值不为空时,user.aa()会触发延迟查询的加载-->
    <setting name="lazyLoadTriggerMethods" value="aa"/>
    

Mybatis注解的使用

在Mybatis 中如果使用注解式开发,那么注解需要添加在Mapper接口中的抽象方法上,在注解中给定需要执行的SQL语句即可,这样就可以不需要映射配置文件。MyBatis 支持纯注解方式,支持纯映射配置文件方式,也支持注解和映射配置文件混合形式。当只有接口没有映射配置文件时,在mybatis-config.xml 引入映射的方式:

  • 指定接口类

    <mapper class="com.bjsxt.mapper.UsersMapper"></mapper>
    
  • 指定接口类所在的包

    <package name="com.bjsxt.mapper"/>
    

1、实现查询

查询所有用户

修改UsersMapper接口

@Select("select * from users")
List<Users> selectUsersAll();

创建测试类

public class SelectUsersAllTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);
        List<Users> list = mapper.selectUsersAll();
        list.forEach(System.out::println);
    }
}

2、注解式开发时的参数传递

  • 顺序传参法
  • POJO传参法
  • Map传参法
// 顺序传参法
@Select("select * from users where username = #{param1} and usersex = #{param2}")
List<Users> selectUsersByNameAndSex(String username,String usersex);

// 顺序传参法
@Select("select * from users where username = #{name} and usersex = #{sex}")
List<Users> selectUsersByNameAndSex1(@Param("name")String username, @Param("sex")String usersex);

// POJO传参法
@Select("select * from users where username = #{username} and usersex = #{usersex}")
List<Users> selectUsersByNameAndSexPojo(Users users);

// Map传参法
@Select("select * from users where username = #{key_name} and usersex = #{key_sex}")
List<Users> selectUsersByNameAndSexMap(Map<String,String> map);

3、实现DML操作

3.1 添加用户

修改UsersMapper接口

@Insert("insert into users values(default,#{username},#{usersex})")
int insertUsers(Users users);

创建测试类

public class InsertUsersTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);

        Users users = new Users();
        users.setUsername("xiazi");
        users.setUsersex("male");
        int i = mapper.insertUsers(users);
        sqlsession.commit();
        System.out.println(i);
    }
}
3.2 更新用户

修改UsersMapper接口

@Update("update users set username = #{username},usersex = #{usersex} where userid = #{userid}")
int updateUsers(Users users);

创建测试类

public class UpdateUsersTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);

        Users users = new Users();
        users.setUserid(20);
        users.setUsername("shitou");
        users.setUsersex("female");
        int i = mapper.updateUsers(users);
        sqlsession.commit();
        System.out.println(i);
    }
}
3.3 删除用户

修改UsersMapper接口

@Delete("delete from users where userid = #{userid}")
int deleteUsersById(int userid);

创建测试类

public class DeleteUsersByIdTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);

        int i = mapper.deleteUsersById(20);
        sqlsession.commit();
        System.out.println(i);
        MybatisUtils.closeSqlSession();
    }
}

4、注解式开发中的动态SQL

在Mybatis 中的注解开发中,对于动态SQL的处理是比较繁琐的,所以如果有动态SQL的操作,建议使用映射配置文件文件方式实现。

4.1 脚本SQL

在script标签中通过动态SQL的标签完成动态SQL的拼接。

修改UsersMapper接口

@Select("<script>select * from users where 1=1 <if test=\"username != null and username != ''\">and username = #{username}</if><if test=\"usersex != null and usersex != ''\">and usersex  = #{usersex}</if></script>")
List<Users> selectUsersByProperty(Users users);

创建测试类

public class SelectUsersByPropertyTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);

        Users users = new Users();
        users.setUsername("huangzi");
        List<Users> list = mapper.selectUsersByProperty(users);
        list.forEach(System.out::println);
    }
}
4.2 在方法中构建SQL

在MyBatis3的注解中包含了@SelectProvider、@UpdateProvider、@DeleteProvider 、@lnsertProvider,这些注解统称为SqlProvider,它们分别对应着查询、修改、删除、新增操作。当使用这些注解时将不在注解中直接编写SQL,而是调用某个类的特定方法来生成SQL。

修改UsersMapper 接口

@SelectProvider(type=UsersMapperProvider.class,method="selectUsersByPropertySQL")
List<Users> selectUsersByPropertyProvider(Users users);

class UsersMapperProvider{
    /**
     * 用于生成动态SQL
     * 传入的users参数,仅用于做条件判断,SQL中的实际参数由mybatis处理
     */
    public String selectUsersByPropertySQL(Users users){
        StringBuffer sb = new StringBuffer("select * from users where 1=1");
        if(users.getUsername() != null && users.getUsername() != ""){
            // 不是 and username = users.getUsername()
            // and前面必须有空格,和1=1隔开
            sb.append(" and username = #{username}");
        }
        if(users.getUsersex() != null && users.getUsersex() != ""){
            sb.append(" and usersex = #{usersex}");
        }
        return sb.toString();
    }
}

创建测试类

public class SelectUsersByPropertyProviderTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);

        Users users = new Users();
        users.setUsername("huangzi");
        List<Users> list = mapper.selectUsersByPropertyProvider(users);
        list.forEach(System.out::println);
    }
}

5、注解式开发中的结果集映射

在Mybatis 中的注解开发中,对于结果集的映射处理也是比较繁琐的,所以如果有结果集映射操作,建议使用映射配置文件文件方式实现。

5.1 映射注解介绍

@Results注解——结果集映射

id:@Results注解的唯一标识

value:包裹多个@Result注解配置的列映射关系

@Result注解——结果集中列的映射

id:是否是主键字段,默认为false,true表示主键

property:实体类的属性名

column:结果集的列名

one:需要使用@one注解(@Result(one = @One()))

many:需要使用@Many 注解(@Result(many = @Many()))

@ResultMap注解

value:通过指定其他@Results 的id的值,复用其他方法上的映射配置

5.2 通过注解实现结果集与对象的映射

修改UsersMapper接口

@Select("select userid as id,username as name,usersex as sex from users where userid = #{userid}")
@Results(id = "usersMapper",value = {
        @Result(id = true,property = "userid",column = "id"),
        @Result(property = "username",column = "name"),
        @Result(property = "usersex",column = "sex")
})
Users selectUsersByIdMapper(int userid);

@Select("select userid as id,username as name,usersex as sex from users where userid = #{userid}")
@ResultMap(value = {"usersMapper"})
Users selectUsersByIdMapper1(int userid);

创建测试类

public class SelectUsersByIdMapperTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);

        Users users = mapper.selectUsersByIdMapper1(1);
        // 如不指定映射关系,SQL运行正常,可查询到数据,但无法返回
        // 加注解指定映射,可查询到数据,可以正常返回
        System.out.println(users);
    }
}

6、一对一关联查询

在Mybatis的注解开发中对于多表查询只支持N+1次查询,不支持连接查询。

创建RolesMapper接口

public interface RolesMapper {

    @Select("select * from roles where user_id = #{userid}")
    Roles selectRolesByUserid(int userid);
}

修改UsersMapper接口

@Select("select * from users where userid = #{userid}")
@Results(id = "",value = {
        @Result(id = true,property = "userid",column = "userid"),
        @Result(property = "username",column = "username"),
        @Result(property = "usersex",column = "usersex"),
        // 不在同一个接口,接口的全名+方法名
        @Result(property = "roles",column = "userid",one = @One(select = "com.bjsxt.mapper.RolesMapper.selectRolesByUserid",fetchType = FetchType.LAZY))
})
Users selectUsersAndRolesByUserid(int userid);

创建测试类

public class SelectUsersAndRolesByUseridTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);
        Users users = mapper.selectUsersAndRolesByUserid(1);
        System.out.println(users);
        System.out.println("--------------");
        System.out.println(users.getRoles());
    }
}

【关于延迟加载的实现】

  • 在全局配置文件中,添加

    <setting name="lazyLoadTriggerMethods" value=""/>
    
  • 在UsersMapper接口中,@Result注解的@One注解的 fetchType属性的值设为 FetchType.LAZY

7、一对多关联查询

创建OrdersMapper接口

public interface OrdersMapper {
    @Select("select * from orders where orderid = #{userid}")
    List<Orders> selectOrdersByUserid(int userid);
}

修改UsersMapper接口

@Select("select * from users where userid = #{userid}")
@Results(id = "usersAndOrdersMapper",value = {
        @Result(id = true,property = "userid",column = "userid"),
        @Result(property = "username",column = "username"),
        @Result(property = "usersex",column = "usersex"),
        // 由userid查询order,所以是column = "userid",而不是column = "orderid"
        @Result(property = "orders",column = "userid",many = @Many(select = "com.bjsxt.mapper.OrdersMapper.selectOrdersByUserid",fetchType = FetchType.LAZY))
})
Users selectUsersAndOrdersByUserid(int userid);

创建测试类

public class SelectUsersAndOrdersByUseridTest {
    public static void main(String[] args) {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlsession.getMapper(UsersMapper.class);
        Users users = mapper.selectUsersAndOrdersByUserid(1);
        System.out.println(users);
        System.out.println("---------");
        List<Orders> list = users.getOrders();
        list.forEach(System.out::println);
    }
}

8、注解式开发与映射配置文件开发的对比

具体在框架中使用注解还是XML配置方式,要视框架情况而定。Spring、SpringBoot中更推荐注解方式。但是在 MyBatis中更推荐使用XML配置方式。原因如下:

  • 使用注解没有实现Java代码和SQL语句的解耦
  • 在SQL语句的动态拼接时比较麻烦
  • 进行多表的查询时配置ResultMap比较麻烦,且不支持连接查询

Mybatis Generator工具的使用

MyBatis Generator (MBG)是MyBatis官方提供的代码生成工具。MyBatis Generator 工具可以根据数据库的表结构自动的帮助我们生成模型、接口与映射配置文件,它可以极大地减少我们对代码的编写,提高开发效率。

1、MyBatis Generator 工具的使用步骤

  1. 在Idea中打开MybatisGenerator项目

    image-20211028172629165
  2. 修改配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
      PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
      "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
       <context id="testTables" targetRuntime="MyBatis3">
          <commentGenerator>
             <!-- 是否去除自动生成的注释 true:是 : false:否 -->
             <property name="suppressAllComments" value="true" />
          </commentGenerator>
          <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
          <jdbcConnection driverClass="com.mysql.jdbc.Driver"
             connectionURL="jdbc:mysql://localhost:3306/bjsxt?useSSL=false" userId="root"
             password="1615">
          </jdbcConnection>
          <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 
             NUMERIC 类型解析为java.math.BigDecimal -->
          <javaTypeResolver>
             <property name="forceBigDecimals" value="false" />
          </javaTypeResolver>
    
          <!-- targetProject:生成PO类的位置 -->
          <javaModelGenerator targetPackage="com.bjsxt.pojo"
             targetProject=".\src">
             <!-- enableSubPackages:是否让schema作为包的后缀 -->
             <property name="enableSubPackages" value="false" />
             <!-- 从数据库返回的值被清理前后的空格 -->
             <property name="trimStrings" value="true" />
          </javaModelGenerator>
            <!-- targetProject:mapper映射文件生成的位置 -->
          <sqlMapGenerator targetPackage="com.bjsxt.mapper"
             targetProject=".\src">
             <!-- enableSubPackages:是否让schema作为包的后缀 -->
             <property name="enableSubPackages" value="false" />
          </sqlMapGenerator>
          <!-- targetPackage:mapper接口生成的位置 -->
          <javaClientGenerator type="XMLMAPPER"
             targetPackage="com.bjsxt.mapper"
             targetProject=".\src">
             <!-- enableSubPackages:是否让schema作为包的后缀 -->
             <property name="enableSubPackages" value="false" />
          </javaClientGenerator>
          <!-- 指定数据库表 -->
          <table schema="" tableName="users"></table>
       </context>
    </generatorConfiguration>
    

在这里插入图片描述

如不设置忽略安全连接,异常信息如下

According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set.
  1. 运行主方法生成POJO类、接口与映射配置文件

在这里插入图片描述

2、生成代码的结构介绍

2.1 模型类

Users 实体类

2.2 Example类(生成查询条件)

UsersExample类封装了针对于Users实体类中所用属性的条件定义。它的作用是通过基于面向对象语法结构来给定操作数据库的条件

2.3 UsersMapper接口(重点)

定义了对数据库CRUD操作的抽象方法,在UsersMapper.xml文件都有与之对应的增删改查标签

2.4 映射配置文件

UsersMapper.xml文件

3、生成代码的使用

3.1 查询

单一条件查询

public class SelectUsersByIdTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        Users users = mapper.selectByPrimaryKey(1);
        System.out.println(users);
    }
}

【异常信息】

Result Maps collection already contains value for com.bjsxt.mapper.UsersMapper.BaseResultMap

【原因】

mybatisgenerator项目中GeneratorSqlmap类的main方法运行了两次,导致生成的UsersMapper.xml文件中内容重复。

多条件查询 and

/**
 * 根据姓名和性别查询
 */
public class SelectUsersByNameAndSexTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        UsersExample usersExample = new UsersExample();
        UsersExample.Criteria criteria = usersExample.createCriteria();
        criteria.andUsernameEqualTo("yanbaobao");
        criteria.andUsersexEqualTo("female");

        List<Users> list = mapper.selectByExample(usersExample);
        list.forEach(System.out::println);
    }
}

多条件查询 or

/**
 * 根据姓名或性别查询
 */
public class SelectUsersByNameOrSexTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        UsersExample usersExample = new UsersExample();
        UsersExample.Criteria criteria = usersExample.createCriteria();
        criteria.andUsernameEqualTo("yanbaobao");
        
        // 另外创建一个Criteria对象,补充查询条件
        UsersExample.Criteria criteria1 = usersExample.createCriteria();
        criteria1.andUsersexEqualTo("robot");
        
        // 补充的查询条件用or()方法连接
        usersExample.or(criteria1);

        List<Users> list = mapper.selectByExample(usersExample);
        list.forEach(System.out::println);
    }
}
3.2 DML操作

添加用户

/**
 * 添加用户
 */
public class InsertUsersTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        
        Users users = new Users();
        users.setUsername("hanbing");
        users.setUsersex("female");
        mapper.insertSelective(users);
        sqlSession.commit();
        MybatisUtils.closeSqlSession();
    }
}

更新用户

/**
 * 更新用户
 */
public class UpdateUsersTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);

        Users users = new Users();
        users.setUsername("zhangsanfeng");
        users.setUsersex("male");
        users.setUserid(49);
        mapper.updateByPrimaryKey(users);
        sqlSession.commit();
        MybatisUtils.closeSqlSession();
    }
}

删除用户

/**
 * 删除用户
 */
public class DeleteUsersTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        mapper.deleteByPrimaryKey(50);
        sqlSession.commit();
        MybatisUtils.closeSqlSession();
    }
}

PageHelper分页插件

PageHelper是一款非常好用的开源免费的Mybatis第三方分页插件。它基于 plugin的方式与Mybatis整合。通过PageHelper所提供的API完成对数据的分页查询。

1、PageHelper使用步骤

  1. 添加jar包
    pagehelper-5.1.11.jar
    jsqlparser-3.1.jar

  2. 在全局配置文件中配置插件

<!--配置helper分页插件-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--设置数据库类型-->
        <property name="helperDialect" value="mysql"/>
    </plugin>
</plugins>
  1. 分页查询API
    • PageHelper.startPage(int pageNum,int pageSize) 给定分页参数,该方法需要在执行查询之前调用

    • pageNum 起始的页数,从1开始计算

    • pageSize 每页显示的条数

    • Pagelnfo对象 存放分页结果对象

    • pagelnfo.getList() 获取分页查询结果

    • pagelnfo.getTotal() 获取查询总条数

    • pageInfo.getPages() 获取总页数。

    • pagelnfo.getPageNum() 获取当前页

    • pagelnfo.getSize() 获取每页显示的条数

2、测试

/**
 * PageHelper分页测试
 */
public class PageHelperTest {
    public static void main(String[] args) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        UsersExample usersExample = new UsersExample();
        // 未创建criteria对象,相当于没有给任何条件,即查询所有

        // 查询第一页,一页包含两行数据
        PageHelper.startPage(1,2);
        List<Users> list = mapper.selectByExample(usersExample);
        list.forEach(System.out::println);

        // 【注意】需要传入查询到的结果集到PageInfo对象中
        PageInfo<Users> pageInfo = new PageInfo<>(list);
        // 总页数
        System.out.println("总页数:"+pageInfo.getPages());
        // 每页数据行数
        System.out.println("每页行数:"+pageInfo.getSize());
        // 当前页
        System.out.println("当前页数:"+pageInfo.getPageNum());
        // 总行数
        System.out.println("总行数:"+pageInfo.getTotal());
        //分页查询结果,和mapper.selectByExample(usersExample)的返回值一样
        List<Users> result = pageInfo.getList();
        result.forEach(System.out::println);
    }
}

Mybatis与Servlet整合

1、搭建环境

创建项目

添加jar包
在这里插入图片描述
添加配置文件

生成POJO类、映射配置文件

2、OpenSessionInView的使用

1、什么是OpenSessionInView

Open Session In View是将一个数据库会话对象绑定到当前请求线程中,在请求期间一直保持数据库会话对象处于Open状态,使数据库会话对象在请求的整个期间都可以使用。直到产生响应后关闭当前的数据库会话对象。

2、创建OpenSessionInViewFilter
package com.bjsxt.web.filter;

import com.bjsxt.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * 在Filter中打开一个sqlSession
 * 拦截所有请求,让其都绑定一个sqlSession
 */
@WebFilter("/*")
public class OpenSqlSessionInViewFilter implements Filter {
    /**
     * 将请求的线程和SqlSession绑定
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        SqlSession sqlsession = MybatisUtils.getSqlSession();
        try{
            filterChain.doFilter(servletRequest,servletResponse);
            sqlsession.commit();
        }catch(Exception e){
            e.printStackTrace();
            sqlsession.rollback();
        }finally{
			MybatisUtils.closeSqlSession();
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
3、实现添加用户的业务

修改index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>

  <a href="addUsers.jsp">添加用户</a>
  
  </body>
</html>

创建addUsers.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>添加用户</title>
</head>
<body>

    <form action="usersServlet.do" method="post">
        <input type="hidden" name="flag" value="addUsers">
        用户姓名:<input type="text" name="username"><br>
        用户性别:<input type="text" name="usersex"><br>
        <input type="submit" value="ok">
    </form>
</body>
</html>

业务层接口

package com.bjsxt.service;

import com.bjsxt.pojo.Users;

public interface UsersService {
    void addUsers(Users users);
}

业务层接口实现类

/**
 * 业务层接口实现类
 */
public class UsersServiceImpl implements UsersService {
    @Override
    public void addUsers(Users users) {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
        mapper.insertSelective(users);
        // 事务处理和关闭sqlSession的try-catch已经挪到了OpenSqlSessionInViewFilter中
    }
}

处理请求

@WebServlet("/usersServlet.do")
public class UsersServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String flag = req.getParameter("flag");
        if("addUsers".equals(flag)){
            this.addUsers(req,resp);
        }
    }

    /**
     * 处理添加用户的请求
     */
    private void addUsers(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        Users users = this.createUsers(req);
        UsersServiceImpl usersService = new UsersServiceImpl();
        usersService.addUsers(users);
        // 为避免表单重复提交,使用重定向方式反馈信息
        resp.sendRedirect("ok.jsp");
    }

    /**
     * 获取用户提交的表单数据
     */
    private Users createUsers(HttpServletRequest req){
        String username = req.getParameter("username");
        String usersex = req.getParameter("usersex");
        Users users = new Users();
        users.setUsername(username);
        users.setUsersex(usersex);
        return users;
    }
}

反馈页面,提示操作成功

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>操作成功</title>
</head>
<body>
    <h2>操作成功!</h2>
</body>
</html>

两次异常的原因:

  • MybatisUtils工具类获取配置文件,文件名错误 is = Resources.getResourceAsStream(“mybatis-config.xml”);
  • 处理请求的Servlet中createUsers()方法的返回值没改,return users,而不是return null
4、实现分页查询用户的业务

【效果】
请添加图片描述

请添加图片描述

修改index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>

  <a href="addUsers.jsp">添加用户</a>&nbsp;&nbsp;&nbsp;&nbsp;
  <a href="findUsers.jsp">查询用户</a>
  
  </body>
</html>

创建findUsers.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>查询用户</title>
</head>
<body>

    <form action="usersServlet.do" method="post">
        <input type="hidden" name="flag" value="findUsers">
        用户姓名:<input type="text" name="username"><br>
        用户性别:<input type="text" name="usersex"><br>
        <input type="submit" value="query">
    </form>

</body>
</html>

修改业务层接口添加方法

public interface UsersService {
    void addUsers(Users users);
    // 分页查询的结果:整页的数据、当前页数、总页数、总行数、每页行数等信息,所以返回值类型为PageInfo
    PageInfo<Users> findUsers(int page,Users users);
}

修改业务层接口实现类添加方法

/**
 * 查询用户
 * @param page
 * @param users
 * @return
 */
@Override
public PageInfo<Users> findUsers(int page, Users users) {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UsersMapper mapper = sqlSession.getMapper(UsersMapper.class);
    UsersExample usersExample = this.createUsersExample(users);

    PageHelper.startPage(page,3);
    // 需要的信息不只是结果集,所以需要PageInfo类型的对象
    List<Users> list = mapper.selectByExample(usersExample);
    PageInfo<Users> pageInfo = new PageInfo<>(list);
    return pageInfo;
}

/**
 * 生成查询条件
 * and连接条件
 */
private UsersExample createUsersExample(Users users){
    UsersExample usersExample = new UsersExample();
    UsersExample.Criteria criteria = usersExample.createCriteria();
    if(users.getUsername() != null && users.getUsername().length() > 0){
        criteria.andUsernameEqualTo(users.getUsername());
    }
    if(users.getUsersex() != null && users.getUsersex().length() > 0){
        criteria.andUsersexEqualTo(users.getUsersex());
    }
    return usersExample;
}

修改Servlet实现处理查询用户的请求

/**
 * 处理查询用户的请求
 */
private void findUsers(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    Users users = this.createUsers(req);
    String pageIndex = req.getParameter("pageIndex");
    int page = 1;
    // 如果从findUsers.jsp页面发送的请求,显然pageIndex为空,就显示第一页内容;如果是上下翻页的操作,pageIndex肯定不为空
    if(pageIndex != null && pageIndex.length() > 0){
        page = Integer.parseInt(pageIndex);
    }

    UsersService usersService = new UsersServiceImpl();
    PageInfo<Users> pageInfo = usersService.findUsers(page, users);

    // 使用请求转发
    req.setAttribute("pageInfo",pageInfo);
    // 同时,还必须传递users,为了维持查询条件的状态
    req.setAttribute("users",users);
    req.getRequestDispatcher("showUsers.jsp").forward(req,resp);
}

在查询结果显示页面实现分页功能

【异常原因】未导入jstl相关jar包,只是给了uri和c.tld文件的路径

使用jstl标签库的准备工作:

  1. 添加两个jar包
  2. 给定c.tld文件的uri和路径

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

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <title>显示查询结果</title>
    <script>
        function subForm(pageIndex){
            document.getElementById("pageIndex").value = pageIndex;
            document.forms[0].submit();
        }
    </script>
</head>
<body>

    <%--获取响应回来的分页信息和查询条件的状态,并再次提交--%>
    <form action="usersServlet.do" method="post">
        <input type="hidden" name="flag" value="findUsers">
        <input type="hidden" name="pageIndex" id="pageIndex">
        <input type="hidden" name="username" value="${users.username}">
        <input type="hidden" name="usersex" value="${users.usersex}">
    </form>
    <table align="center" border="1px" width="40%">
        <tr>
            <th>用户ID</th>
            <th>用户姓名</th>
            <th>用户性别</th>
        </tr>
        <c:forEach items="${pageInfo.list}" var="users">
            <tr>
                <td>${users.userid}</td>
                <td>${users.username}</td>
                <td>${users.usersex}</td>
            </tr>
        </c:forEach>
        <tr>
            <td colspan="3" align="center">
                <%--为什么不能直接跳转?因为当前页面还保存着查询条件的状态--%>
                <c:if test="${pageInfo.pageNum > 1}">
                    <a href="#" οnclick="subForm(${pageInfo.pageNum-1})">上一页</a>
                </c:if>
                <c:forEach begin="1" end="${pageInfo.pages}" varStatus="st">
                    <c:choose>
                        <c:when test="${pageInfo.pageNum eq st.count}">
                            <a style="color:red" href="#" οnclick="subForm(${st.count})">${st.count}</a>
                        </c:when>
                        <c:otherwise>
                            <a href="#" οnclick="subForm(${st.count})">${st.count}</a>
                        </c:otherwise>
                    </c:choose>

                </c:forEach>
                <c:if test="${pageInfo.pageNum < pageInfo.pages}">
                    <a href="#" οnclick="subForm(${pageInfo.pageNum+1})">下一页</a>
                </c:if>

            </td>
        </tr>
    </table>

</body>
</html>
5、【分析】实现了哪些功能?如何实现?有哪些关键点?
5.1 展示分页查询的结果
关于为什么`PageInfo<Users> findUsers(int page,Users users);`需要传入users?

PageInfo类的父类PageSerializable<T>有一个List<T>类型的属性list,获取pageInfo对象就要传入List<Users>

resp对象响应回的结果:pageInfo对象和users对象,pageInfo对象包含结果集和分页信息,users对象包含查询条件。使用EL表达式获得结果集包含的数据,使用jstl核心标签库c:forEach标签遍历展示。

5.2 翻页

在表中新建一行,合并为一列,创建两个超链接,分别是上一页和下一页;href属性中给#,不直接跳转,而是绑定单击事件,函数中传递的参数为页码,上一页=当前页码-1,下一页=当前页码+1;使用c:if标签将翻页按钮包裹,设置条件,如果页码 >1就有上一页,如果页码 <总页数就有下一页;subFrom函数的作用:将要查询的页码赋值给表单要提交信息中的pageIndex属性,document.forms[0].submit();实现了隐藏表单( flag = “findUsers”)的提交和对usersServlet.do的请求访问,该属性的值将通过请求传递给Servlet,实现新的一次分页查询。

5.3 定位当前页码

使用c:forEach标签遍历展示从1-总页数的数字,这些数字表示的页码用a标签包裹,绑定单击事件将当前点击的页码传入subFrom函数中实现跳转。使用c:choose``c:when``c:otherwise标签进行判断,如果当前页码变色

5.4 查询状态的保持

第一次查询请求回的响应信息中包含Users对象,通过EL表达式可以获取其中属性username、usersex的值,点击页码或翻页再次发出请求同时提交属性username、usersex的值,保持了查询条件的状态。

【测试】注释掉form表单中的username、usersex

在这里插入图片描述

  • 不指定查询条件,查询、翻页和点击页码均正常
  • 指定姓名和性别查询,可查询,翻页或点击页码返回不指定查询条件返回的结果集
  • 指定性别查询,可查询,翻页或点击页码返回不指定查询条件返回的结果集

说明查询条件的状态未保持,所以再次提交username、usersex是必须的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值