[SSM框架]—Mybatis入门

前言

这学期开了Javaweb的课,本来想着顺便跟着学学,没想到一节公开课,一节实验课,课堂效果依旧那样还是自己学吧。

Mybatis搭建

下载

Releases · mybatis/mybatis-3 (github.com)

环境

apache-maven:Maven – Download Apache Maven

MySQL版本:MySQL 5.7

MyBatis 版本: MyBatis 3.5.10

创建maven工程

设置好maven选项

在这里插入图片描述

新建个maven项目,修改打包方式:jar ,pom.xml加上<packaging>jar</packaging>即可

依赖

<dependencies>
    <!-- Mybatis核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.3</version>
    </dependency>
</dependencies>

创建个数据库

在这里插入图片描述

创建Mybatis核心文件

在resources中创建核心文件mybatis-config.xml

驱动类driver-class-name

mysql5的JDBC驱动,使用com.mysql.jdbc.Driver

mysql8的JDBC驱动,使用com.mysql.cj.jdbc.Driver

链接地址url

mysql5的url:jdbc:mysql://localhost:3306/ssm

mysql8的url:jdbc:mysql://llocalhost:3306/ssm?serverTimezone=UTC

我用的是mysql5所以对应的driver和url也是5版本的,value、password就是数据库的用户名密码,这里的最下边的mappermybatis映射文件在后边会创建

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--设置连接数据库的环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/Mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入mybatis映射文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

创建mapper接口

package com.sentiment.mapper;

public interface UserMapper {

    int insertUser();

}

创建mybatis映射文件

相关概念:ORM(Object Relationship Mapping)对象关系映射。

  1. 对象:Java的实体类对象
  2. 关系:关系型数据库
  3. 映射:二者之间的对应关系
Java概念数据库概念
属性字段/列
对象记录/行

1、映射文件的命名规则: 表所对应的实体类的类名+Mapper.xml 例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml 因此一个映射文件对应一个实体类,对应一张表的操作 MyBatis映射文件用于编写SQL,访问以及操作表中的数据 MyBatis映射文件存放的位置是src/main/resources/mappers目录下

2、MyBatis中可以面向接口操作数据,要保证两个一致: a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致 b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

注意事项:

mapper接口和映射文件要保证两个一致:

1.mapper接口的全类名和映射文件的namespace一致

2.mapper接口中的方法的方法名要和映射文件中的sqL的id保持一致

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.sentiment.mapper.UserMapper">
    <insert id="insertUser">
        insert into t_user values(null,'Sentiment','123456',20,'','123456@qq.com')
    </insert>
</mapper>

之后将mybatis-config.xml中的mapper映射文件加进去,即:

<mappers>
    <mapper resource="mappers/UserMapper.xml"/>
</mappers>

最后的目录结构:

在这里插入图片描述

Demo

package com.sentiment.test;

import com.sentiment.mapper.UserMapper;
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 org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class MybatisTest {

    @Test
    public void testInsert() throws IOException {
        //获取Mybatis核心配置文件的输入流
        InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //获取sql的会话对象SqlSeesion,是MyBatis提供的操作数据库的对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //如果加上参数true则操作的sql对象则会自动提交,无需commit方法
        //SqlSession sqlSession = sqlSessionFactory.openSession(true);
        //通过代理模式创建UserMapper接口的代理实现类对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //调用mapper接口中的方法,实现插入功能
        int result = mapper.insertUser();
        System.out.println(result);
        //提交事务
        sqlSession.commit();
        sqlSession.close();

    }

}

执行后则会根据UserMapper.xml中的文件执行插入语句

  • SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的 会话)
  • SqlSessionFactory:是“生产”SqlSession的“工厂”。
  • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的 相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

功能优化

下边实现insertUser功能的语句还可以进行优化

优化前:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.insertUser();

优化后:

int result = sqlSession.insert("com.sentiment.mapper.UserMapper.insertUser");
log4j日志

依赖

<!-- log4j日志 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>

配置好后运行,则会输出日志信息

日志的级别 FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

从左往右日志信息越来越详细

增删改查

先写个工具类

package com.sentiment.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 SqlSeesionUtils {
    public static SqlSession getSqlSession(){
        SqlSession sqlSession;
        try {
            InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
            sqlSession = build.openSession(true);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return sqlSession;
    }
}

修改配置文件UserMapper.xml,加上update语法

<update id="updateUser">
    update t_user set username='tana' where id=2;
</update>

接口类也实现一下对应的方法

public interface UserMapper {

    int insertUser();

    int updateUser();
}

之后定义个修改的方法testUpdate(),直接通过定义的工具类中获取SqlSeesion即可

public void testUpdate(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int result = mapper.updateUser();
    System.out.println(result);
    sqlSession.close();
}

和上边同理

UserMapper.xml

<delete id="deleteUser">
    delete from t_user where id=3;
</delete>

添加接口

int deleteUser();

最终实现

public void testDelete(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int result = mapper.deleteUser();
    System.out.println(result);
    sqlSession.close();
}

接口

User getUserId();

List<User> getUserList();

UserMaaper.xml

<select id="getUserId" resultType="com.sentiment.pojo.User">
    select * from t_user where id=1
</select>

<select id="getUserList" resultType="com.sentiment.pojo.User">
    select * from t_user;
</select>

实现

@Test
public void testGetUserId(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserId();
    System.out.println(user);
    sqlSession.close();
}

@Test
public void testGetUserList(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUserList();
    userList.forEach(System.out::println);
}

注意:

1、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系 resultType:自动映射,用于属性名和表中字段名一致的情况 resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况

2、当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常 TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

配置文件说明

常见的配置属性

configuration(配置)

  • properties(属性)
  • settings(设置)
  • typeAliases(类型别名)
  • environments(环境配置)
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
  • mappers(映射器)

注意:mybatis配置文件中标签的顺序是指定的依次是:

properties(属性)->settings(设置)->typeAliases(类型别名)->environments(环境配置)->mappers(映射器)

properties(属性)

设置个jdbc.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/Mybatis
jdbc.username=root
jdbc.password=123456

导入到配置文件mybatis-config.xml中

<!--引l入properties文件,此后就可以在当前文件中使用${ key}的方式访问value-->
<properties resource="jdbc.properties"/>

之后就可以将datasource部分进行替换

<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>

environments(环境配置)

<?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>
    <!--引l入properties文件,此后就可以在当前文件中使用${ key}的方式访问value-->
    <properties resource="jdbc.properties"/>
    <!--
        environments:配置链接数据库的环境
        属性:default:设置默认使用环境的id
    -->
    <environments default="development">
        <!--
            environment:设置一个具体的链接数据库的环境
            属性:id:设置数据库的唯一表示,不能重复
        -->
        <environment id="development">
            <!--
                transactionManager:设置事务管理器
                属性:
                type:设置事务管理的方式
                type="JDBC /MANAGED”
                JDBC:表示使用JDBC中原生的事务管理方式
                MANAGED:被管理,例如Spring
            -->
            <transactionManager type="JDBC"/>
            <!--
                datasource:设置数据源
                属性:
                type:设置数据源的类型
                type="PoOLED/ UNPOOLED | JNDI"
                POOLED:表示使用数据库连接池
                UNPOOLED:表示不使用数据库连接池了
                JNDI :表示使用上下文中的数据源
            -->
            <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>
        
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/Mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入mybatis映射文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

typeAliases(类型别名)

注:该标签根据顺序需放在properties和setting标签后

<typeAliases>
    <!-- type:需要起别名的类型,alias:别名-->
    <!-- <typeAlias type="com.sentiment.pojo.User" alias="xxx"></typeAlias> -->
    
    <!--若不设置alias别名,则默认将类名当成别名,且不区分大小写-->
    <!-- <typeAlias type="com.sentiment.pojo.User"></typeAlias> -->
    
    <!-- 通过包名设置类型别名,默认将类名当成别名,且不区分大小写-->
    <package name="com.sentiment.pojo"/>
</typeAliases>

mapper(映射器)

若需要同时导入多个mapper.xml配置文件时,也可以包的形式全部导入,但需要满足如下两点:

<mappers>
    <!-- <mapper resource="mappers/UserMapper.xml"/> -->
    <!--
    以包的方式引入映射文件,但是必须满足两个条件:
     1、mapper接口和映射文件所在的包必须一致
     2、mapper接口的名字和映射文件的名字必须一致
     -->
    <package name="com.sentiment.mapper"/>
</mappers>

创建核心文件和映射文件模板

File -> Setting -> Editor -> File and Code Templates

在这里插入图片描述

Mybatis获取参数的两种方式

MyBatis获取参数值的两种方式:${}和#{}

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

KaTeX parse error: Expected 'EOF', got '#' at position 51: …赋值时,需要手动加单引号;但是#̲{}使用占位符赋值的方式拼接s…{}可以有效的避免sql注入)

单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以任意的名称获取参数的值,…{}需要手动加单引号

接口

User getUsername(String username);

mapper配置文件

<select id="getUsername" resultType="User">
    <!-- select * from t_user where username=#{username} -->
    select * from t_user where username='${username}'
</select>

实现

public void GetUsername(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user=mapper.getUsername("Sentiment");
    System.out.println(user);
}

多个字面量类型的参数

若mapper接口方法的参数为多个的字面量类型

此时Mybatis会将参数放在map集合中,以两种方式存储(也可arg,param混合使用):

  • 以arg0,arg1…为键,以参数为值
  • 以param1,param2…为键,以参数为值

接口

User checklogin(String username ,String password);

配置文件

<select id="checklogin" resultType="User">
    <!--select * from t_user where username=#{arg0} and password=#{arg1}-->
    select * from t_user where username='${param1}' and password='${param2}'
</select>

通过Map自定义参数

若mapper接口中的方法需要多个参数时,此时可以手动创建map集合,讲这些数据放在map中,并通过${}或#{}访问map集合的键就可以获取对应的值

接口

User checkLoginByMap(Map<String,Object> map);

配置文件

<select id="checkLoginByMap" resultType="User">
    <!-- select * from t_user where username=#{username} and password=#{password};-->
    select * from t_user where username='${username}' and password='${password}'
</select>

实现

public void CheckLoginByMap(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Object> map = new HashMap<>();
    map.put("username","Sentiment");
    map.put("password",123456);
    User user=mapper.checkLoginByMap(map);
    System.out.println(user);
}

实体型类型的参数

需要通过#{}和${}访问实体类中的属性就可以获取相对应的属性值

接口

int insertUser(User user);

配置文件

<insert id="insertUser">
    insert into t_user values(#{id},#{username},#{password},#{age},#{gender},#{email})
</insert>

实现

public void insertUser(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user=new User(4,"xxx","123456",20,"男","123456@qq.com");
    mapper.insertUser(user);
}

@Param注解

通过接口定义@Param,此时mybatis会将注解中的参数放在map中进行存储,有两种存储方式:

  • 以@Param注解中的value属性值为键,以参数为值
  • 以param1,param2…为键,以参数为值

接口

User checkLoginByParam(@Param("username") String username,@Param("password") String password);

配置文件

<select id="checkLoginByParam" resultType="User">
    <!-- select * from t_user where username=#{param1} and password=#{param2} -->
    select * from t_user where username=#{username} and password=#{password}
</select>

实现

public void checkLoginByParam(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user=mapper.checkLoginByParam("Sentiment","123456");
    System.out.println(user);
	}
}

Mybatis查询功能

将单行数组存入map中

接口

Map<String,Object> getUserByIdToMap(@Param("id") int id);

配置文件

<select id="getUserByIdToMap" resultType="map">
    select * from t_user where id=#{id}
</select>

实现

public void getUserByIdToMap(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
    Map<String, Object> map = mapper.getUserByIdToMap(2);
    System.out.println(map);
}

将多行数组存入map中

由于是多个map并不能放到一个map中,所以这里用的是list

接口

List<Map<String ,Object>> getAllUser();

但除此外,Map可以通过@MapKey来存储键值,从而完成map中存储多条数据

@MapKey("id")
Map<String ,Object> getAllUser();

执行特殊SQL语句

模糊查询

用like关键字实现模糊查询

接口

List<User> getAllUserBySpecial(@Param("mohu") String mohu);

配置文件

有以下三种查询方式:

<select id="getAllUserBySpecial" resultType="User">
    <!-- select * from t_user where username like '%${mohu}%' -->
    <!-- select * from t_user where username like "%"#{mohu}"%" -->
    select * from t_user where username like concat('%',#{mohu},'%')
</select>

实现

这里以模糊查询username带字母"n"的为例

@Test
public void getAllUserBySpecial(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    SpecialSqlMapper mapper = sqlSession.getMapper(SpecialSqlMapper.class);
    List<User> name = mapper.getAllUserBySpecial("n");
    name.forEach(System.out::println);
}

批量删除

接口

int deleteMore(@Param("ids") String ids);
#也可以用between and 的方式,但这种方式需要两个参数start,end
int deleteMore(@Param("ids") String ids,@Param("ide")String ide);

配置文件

delete from t_user where id in (${ids})
#between
delete from t_user where id between ${ids} and ${ide}

实现

public void deleteMore(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    SpecialSqlMapper mapper = sqlSession.getMapper(SpecialSqlMapper.class);
    mapper.deleteMore("5,6");
    #mapper.deleteMore("5","6"); #between方式 
}

动态设置表名

接口

List<User> changeTableName(@Param("tablename") String tablename);

配置文件

<select id="changeTableName" resultType="User">
    select * from ${tablename}
</select>

实现

public void changeTableName(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    SpecialSqlMapper mapper = sqlSession.getMapper(SpecialSqlMapper.class);
    List<User> list = mapper.changeTableName("t_user");
    list.forEach(System.out::println);
}

获取自增的主键

这种方式主要建立于主键自增的基础上,可以通过mybatis语句,获取自增后的属性值

接口

void insertUser(User user);

配置文件

  • useGeneratedKeys:表示当前添加功能使用自增的主键
  • keyProperty:将添加的数据的自增主键为实体类类型的参数赋值
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>

实现

public void insertUser(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    SpecialSqlMapper mapper = sqlSession.getMapper(SpecialSqlMapper.class);
    User user = new User(null, "xxx", "123456", 20, "男", "123456@qq.com");
    mapper.insertUser(user);
    System.out.println(user);
}

未设置时:

在这里插入图片描述

设置后:

在这里插入图片描述

自定义resultMap

resultMap处理字段和属性的映射关系

先建两张表

在这里插入图片描述

在这里插入图片描述

并创建对应的pojo和mapper

使用全局配置解决属性和字段名不一致问题

字段设置的是emp_id,emp_name,而在java中应用的是驼峰命名法,所以定义的属性名是empId,empName,这就导致了一个属性名不同而无法完成映射进行查询的问题

接口

Emp getUserByEmpId(@Param("id") int id);

配置文件

主要有三种方式

  • 第一种就是下边注释中的起别名
<select id="getUserByEmpId" resultType="Emp">
    <!-- select emp_id empId ,emp_name empName ,age ,gender from t_emp where emp_id=${id} -->
    select * from t_emp where empid=${id}
</select>
  • 而第二种就是在mybatis配置文件中使用全局settings(注:settings属性应该放在properties后)
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

实现

public void getUserByEmpId(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp result = mapper.getUserByEmpId(1);
    System.out.println(result);
}
  • 第三种是使用resultMap自定义映射
<resultMap id="empresultMap" type="emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
</resultMap>

<select id="getUserByEmpId" resultMap="empresultMap">
    select * from t_emp where emp_id=${id}
</select>

resultMap:设置自定义映射关系

id:唯一标识

type:处理映射关系的实体类类型

常用标签

  • id:处理主键和实体类中属性的映射关系
  • result:处理普通字段和实体类中属性的映射关系
  • column:设置映射关系中的字段名
  • property:设置映射关系中的属性的属性名

处理多对一映射关系

通过员工id查询对应部门名称等信息

接口

Emp getEmpAndDeptByEmpId(@Param("id") int id);

配置文件

<select id="getEmpAndDeptByEmpId" resultType="Emp">
    select * from t_emp left join t_dept on t_emp.dept_id=t_dept.dept_id where emp_id=${id}
</select>

实现

public void getEmpAndDeptByEmpId(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp result = mapper.getEmpAndDeptByEmpId(1);
    System.out.println(result);
}

查询结果

Emp{empId=1, empName='Sentiment', age=20, gender='男', dept=null}

dept值为null,采用左链接的方式进行查询,但在Emp类中定义的是private Dept dept;,无法将属性名dept对应到数据库中的字段dept_id、dept_name,且需要注意的另一个问题是 resultType返回的是Emp类型的,而查询的t_dept中的数据是Dept的实现类型,明显不可以,所以有了下边三种解决办法

使用级联处理

同解决属性和字段名不一致的情况,设置一个resultMap自定义映射

由于属性dept属性是Dept类型的,所以dept.deptId就可以代表Dept类中的deptId属性

<resultMap id="EmpAndDeptByEmpId" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="dept_id" property="dept.deptId"></result>
    <result column="dept_name" property="dept.deptName"></result>
</resultMap>

<select id="getEmpAndDeptByEmpId" resultMap="EmpAndDeptByEmpId">
    select * from t_emp left join t_dept on t_emp.dept_id=t_dept.dept_id where emp_id=${id}
</select>

此时的运行结果

Emp{empId=1, empName='Sentiment', age=20, gender='男', dept=Dept{deptId=1, deptName='A'}}

association

association :处理多对一的映射关系(处理实体类类型的属性)

property:设置需要处理映射关系的属性的属性名

javaType:设置要处理的属性的类型

<resultMap id="EmpAndDeptByEmpId" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    
    <association property="dept" javaType="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
    </association>
</resultMap>

分布查询

property:设置需要处理映射关系的属性的属性名

select:可以理解为property中属性的值是从select中的类获取来的

column:将查询出的某个字段作为分布查询的sql条件

接口

Emp getEmpAndDeptByStepOne(@Param("id") int id);

配置文件

<resultMap id="EmpAndDeptByEmpId" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <association property="dept"
                 select="com.sentiment.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                 column="dept_id">
    </association>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="EmpAndDeptByEmpId">
    select * from t_emp where emp_id = #{id}
</select>

新建个DeptMapper接口

Dept getEmpAndDeptByStepTwo(@Param("deptId") int deptId);

并设置对应的DeptMapper.xml配置文件

<mapper namespace="com.sentiment.mapper.DeptMapper">
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from t_dept where dept_id=#{deptId}
    </select>
</mapper>

实现

public void getEmpAndDeptStep(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp result = mapper.getEmpAndDeptByStepOne(1);
    System.out.println(result);
}

捋一下思路:

先调用getEmpAndDeptByStepOne(1)方法,之后会执行配置文件中定义的语句—select * from t_dept where dept_id=#{deptId}

由于设置的是resultMap自定义映射,因此会将上边column中自定义执行语句中的dept_id结果,当做条件去执行

getEmpAndDeptByStepTwo()方法,也就执行了select * from t_dept where dept_id=#{deptId},而deptId由于设置了settings也

就相当于获取dept_id的值,而这个值就是column中设置的通过getEmpAndDeptByStepOne()获取的dept_id

延迟加载

分布查询的一个优点就是延迟加载

假设前边的输出结果换成只输出员工名

System.out.println(result.getEmpName());

此时执行后,可以发现若只需获取员工名,其实只从t_emp进行查询就可以了,但是分布查询仍然会执行查询t_dept的语句,影响了查询速度等。

在这里插入图片描述

此时就可以用延时加载来节约资源,但在使用前需要进行全局配置

  • lazyLoadingEnabled:延时加载开关,默认值为false,这里需要开启所以设为了true
  • aggressiveLazyLoading:开启后,任何方法的调用都会加载该对象的所有属性。否则,会按需加载,默认值为false。这里目的是按需加载所以设为false,默认就是false所以是不需要设置该选项,但是作为初学需要了解一下
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>

设置后mybatis就会按需查询,不再调用t_dept表

在这里插入图片描述

前边两个是全局配置,而如果设置多个分布查询后,若某个查询无需分布查询,则可在对应的mapper中设置fetchType属性即可

<association property="dept" fetchType="eager"
             select="com.sentiment.mapper.DeptMapper.getEmpAndDeptByStepTwo"
             column="dept_id">
</association>
  • fetchType=“lazy(延时加载) | eager(立即加载)”

这里设置的是eager所以,本次查询将不再适用延时加载,在获取员工姓名时仍会查询t_dept表

处理一对多映射

collection

collection:处理一对多的映射关系(处理集合类型的属性)与association相对应

对多用集合private List<Emp> emps;,在Dept类中添加个属性,并设置对应的setter,getter,toString等

接口

Dept getDeptAndEmpByDeptId(@Param("deptId") int deptId);

DeptMapper.xml

property:设置需要处理映射关系的集合名

ofType:设置集合类型的属性中存储的数据类型 与javaType相对应只是一个设置属性类型一个是集合类型

<resultMap id="deptAndEmpResultMap" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps" ofType="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
    </collection>
</resultMap>

<select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
    select * from t_dept left join t_emp on t_dept.dept_id=t_emp.dept_id where t_dept.dept_id=#{deptId}
</select>

实现

public void getDeptAndEmpByDeptId(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    Dept result = mapper.getDeptAndEmpByDeptId(1);
    System.out.println(result);
}

分布查询

为了更好的理解这里将两步分开一步步来

先定义Dept接口

Dept getDeptAndEmpByStepOne(@Param("deptId") int deptId);

之后本次查询是一对多的查询,所以现将t_dept表查出来,resultMap等等以后再填

<select id="getDeptAndEmpByStepOne" resultMap="">
    select * from t_dept where dept_id =#{deptId}
</select>

接着定义resultMap,要获取的是员工集合,所以property填之前定义好的集合emps,而select的意思是emps集合的类型是从哪获取的,所以先空出来去定义对应的集合类型

<resultMap id="deptAndEmpByStepOne" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps"
                select=""
                column="">
    </collection>
</resultMap>

定义Emp集合接口

List<Emp> getDeptAndEmpByStepTwo(@Param("id") int id);

通过配置文件实现

<select id="getDeptAndEmpByStepTwo" resultType="Emp">
    select * from t_emp where emp_id = #{id}
</select>

现在就有了select即getDeptAndEmpByStepTwo的路径,而两个库是通过dept_id链接的,所以填写到column中,最后再将select标签中的resultMap填上

<resultMap id="deptAndEmpByStepOne" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps"
                select="com.sentiment.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                column="dept_id">
    </collection>
</resultMap>

<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepOne">
    select * from t_dept where dept_id =#{deptId}
</select>

实现

public void getDeptAndEmpByStep(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    Dept result = mapper.getDeptAndEmpByStepOne(1);
    System.out.println(result);
    //由于前边设置了延迟加载所以,也可以通过下边方式进行测试
    System.out.println(result.getDeptName());
}

由于一对多是集合的方式,不像多对一时通过Dept类型的dept属性进行映射(<result column="dept_id" property="dept.deptId"></result>),所以级联便不再适用

动态SQL

if标签

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行

接口

List<Emp> getEmpByCondition(Emp emp);

配置文件

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp where
    <if test="empName !=null and empName !=''">
        emp_name=#{empName}
    </if>
    <if test="age !=null and age !=''">
        and age=#{age}
    </if>
    <if test="gender !=null and gender !=''">
        and gender=#{gender}
    </if>
</select>

实现

public void getEmpByCondition(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
    Emp emp = new Emp(1, "Sentiment", 20, "男");
    List<Emp> result = mapper.getEmpByCondition(emp);
    result.forEach(System.out::println);
}

where标签

在使用if标签时存在一个问题:若第一个条件不成立,第二个条件成立的话 ,则第一个标签不会执行,但由于第二个标签中存在and关键字若第一个标签不执行直接执行第二个标签的话,会将and直接拼接到where 后边,导致语句执行失败即:

在这里插入图片描述

此时就有了三个解决办法:

1=1恒成立

在where后边加上恒成立条件1=1,之后的每个if标签的语句前都加上and,此时无论标签执不执行,语句都成立

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp where 1=1
    <if test="empName !=null and empName !=''">
        and emp_name=#{empName}
    </if>
    <if test="age !=null and age !=''">
        and age=#{age}
    </if>
    <if test="gender !=null and gender !=''">
        and gender=#{gender}
    </if>
</select>

where标签

在if标签前加上where标签:

  • 若where标签中有条件成立,会自动生成where关键字
  • where标签会自动将内容中前边多余的and去掉
  • 若where标签中没有任何一个条件成立,则where没有任何功能
<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <where>
        <if test="empName !=null and empName !=''">
            emp_name=#{empName}
        </if>
        <if test="age !=null and age !=''">
            and age=#{age}
        </if>
        <if test="gender !=null and gender !=''">
            and gender=#{gender}
        </if>
 </where>
</select>

trim标签

除了前两种方法外,还可以使用trim标签

prefix/suffix:在内容前/后加上指定内容

prefixOverrides/suffixOverrides:在内容前/后去掉多余的指定内容

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <trim prefix="where" prefixOverrides="and">
        <if test="empName !=null and empName !=''">
            emp_name=#{empName}
        </if>
        <if test="age !=null and age !=''">
            and age=#{age}
        </if>
        <if test="gender !=null and gender !=''">
            and gender=#{gender}
        </if>
    </trim>
</select>

choose、when、otherwise

when相当于ifelse、otherwse相当于else,所以when至少有一个而otherwise至多有一个,都放在choose标签中

when语句中不需要加and,因为when相当于ifelse语句,所以三个when标签至多只会执行一个

<select id="getEmpByChoose" resultType="Emp">
    select * from t_emp
    <where>
        <choose>
            <when test="empName !=null and empName !=''">
                emp_name=#{empName}
            </when>
            <when test="age !=null and age !=''">
                age=#{age}
            </when>
            <when test="gender !=null and gender !=''">
                gender=#{gender}
            </when>
        </choose>
    </where>
</select>

foreach标签

collection:设置循环中的数组或集合

item:表示数组或集合中的每一个数组

separator:每次循环之间数据的分隔符

index:指定一个名字,用于表示在循环过程中,每次循环到的位置

open:表示该循环语句以什么开始

close:表示以什么结束

批量添加(集合)

接口

void insertMoreEmp(@Param("emps") List<Emp> emps);

配置文件

由于emps是集合类型,所以需要用item=emp,设置每次执行的迭代名,separator=","以逗号分割每次循环

<insert id="insertMoreEmp">
    insert into t_emp values
    <foreach collection="emps" item="emp" separator=",">
        (#{emp.empId},#{emp.empName},#{emp.age},#{emp.gender},null)
    </foreach>
</insert>

实现

public void insertMoreEmp(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
    Emp emp1 = new Emp(5, "aaa", 20, "男");
    Emp emp2 = new Emp(6, "bbb", 20, "男");
    Emp emp3 = new Emp(7, "ccc", 20, "男");
    List<Emp> emps = Arrays.asList(emp1, emp2, emp3);
    mapper.insertMoreEmp(emps);
}

批量删除(数组)

接口

void deleteMoreEmp(@Param("empids") int[] empids);

配置文件

open、close设置语句开头结尾,分隔符也可以用or

<delete id="deleteMoreEmp">
    delete from t_emp where emp_id in
    <foreach collection="empids" item="empid" separator="," open="(" close=")">
        #{empid}
    </foreach>
</delete>

实现

public void deleteMoreEmp(){
    SqlSession sqlSession = SqlSeesionUtils.getSqlSession();
    DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
    int[] ints = {5, 6, 7};
    mapper.deleteMoreEmp(ints);
}

sql标签

可以记录一段sql,在需要用的她方使用include标签进行引用

<sql id="sqls">
    emp_id,emp_name,age,gender
</sql>
<select id="getEmpByCondition" resultType="Emp">
    select <include refid="sqls"></include> from t_emp
</select>

Mybatis缓存

一级缓存

MyBatis的一级缓存是sqlSession级别的,即通过同一个sqLSession查询的数据会被缓存,再次使用同一个sqlsession查询同一条数据,会从缓存中获到

emp1、emp2是从同一个sqlSeeion获取的,因此在查询同一条语句时,查询语句只执行了一次,第二次则会从缓存中查找

在这里插入图片描述

一级缓存失效情况

  • 不同的sqLsession对应不同的一级缓存
  • 同一个sqlsession但是查询条件不同
  • 同一个sqLsession两次查询期间执行了任何一次增删改操作
  • 同一个sqlsession两次查询期间手动清空了缓存

1、若在创建sqlSeesion2,区别于前一个sqlSession时,即使查询同一条语句 也会重新执行

在这里插入图片描述

2、前边的查询的是id=1,若后边改成id=2,缓存就会失效

Emp emp1=mapper1.getEmpById(1);
System.out.println(emp1);
Emp emp2 = mapper1.getEmpById(2);
System.out.println(emp2);

3、当执行插入或删除语句后,会自动清理缓存

Emp emp1=mapper1.getEmpById(1);
System.out.println(emp1);

mapper1.insertEmp(new Emp(5,"aaa",20,"男"));
Emp emp2 = mapper1.getEmpById(1);
System.out.println(emp2);

4、手动清理缓存后也会重新执行

Emp emp1=mapper1.getEmpById(1);
System.out.println(emp1);

sqlSession1.clearCache();
//mapper1.insertEmp(new Emp(5,"aaa",20,"男"));
Emp emp2 = mapper1.getEmpById(1);
System.out.println(emp2);

二级缓存

MyBatis的二级缓存是sqLsessionFactory级别的,即通过同一个sqLsessionFactory所获取的sqLsession查询的数据会被缓存,在通过同一个sqLSessionFactory所获取的sqlSession查询相同的数据会从缓存中获取

MyBatis二级缓存开启的条件:

  • 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在sqlsession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

二级缓存由于是sqlSessionFactory级别的,所以不再适用工具类,在满足上述条件后成功从缓存读取数据并且在二级缓存中会返回命中率等信息,但需要注意的是(存在增删改的操作时仍会清除缓存)

在这里插入图片描述

二级缓存相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性:缓存回收策略,默认的是 LRU。

    • LRU (Least Recently Used)-最近最少使用的:移除最长时间不被使用的对象。
    • FIFO (First in First out)-先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT-软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK-弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  • flushInterva属性:刷新间隔,单位毫秒

    • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
  • size属性:引用数目,正整数

    • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly属性:只读,true/false

    • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
    • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

自定义缓存ehcache

依赖

<!-- ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

<!-- slf4j日志门面的一个具体实现-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

ehcache.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">

    <diskStore path="D:/Java_Web/Mybatis_cache/cache"/>

    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>

    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>

二级缓存类型

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
    1、配置控制台的日志输入
    -->
    <!--    1.1、CONSOLE :表示当前的日志信息是可以输出到控制台的。-->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--           1.2、配置每行日志中 level、时间、msg等的【先后顺序】以及对应的【颜 🚩 色】-->
            <!--               详细内容见官方链接:https://logback.qos.ch/manual/layouts.html-->
            <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>


    <!--
    2、配置日志输入的级别
        level(默认debug):用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
    -->

    <!--    2.1、设置某一个包或具体的某一个类的日志打印级别-->
    <!--    <logger name="com.lencamo" level="DEBUG" additivity="false">
            <appender-ref ref="Console"/>
        </logger>-->

    <!--   2.2、设置root下总的日志打印级别-->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
    </root>

    <logger name="com.sentiment.mapper" level="DEBUG"/>
</configuration>

此时执行,则会返回logback日志信息,并在指定目录生成缓存文件

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值