MyBatis的简介与使用

Mybatis

JDBC操作数据库的缺点

  • 存在大量的冗余代码。
  • 手工创建 Connection、Statement 等,效率低下。
  • 手工将结果集封装成实体对象。
  • 查询效率低,没有对数据访问进行优化。

Mybatis框架

简介

MyBatis 本是 apache 的一个开源项目 iBatis, 2010年这个项目由 apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

iBatis 一词来源于 “internet” 和 “abatis” 的组合,是一个基于Java的持久层框架。iBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAOs)

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

Mybatis获取

官网:https://mybatis.org/mybatis-3/

Maven配置:

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>

使用Mybatis

工程搭建

引入依赖库:

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.17</version>
</dependency>

config配置文件

在resources目录下创建config.xml

1.配置JDBC环境;

2.注册Mapper。

<?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">
<!--MyBatis配置-->
<!--常用配置标签的先后顺序properties,settings,typeAliases,typeHandlers,plugins,environments,mappers如果配置文件中同时存在这些配置标签,它们之间的顺序必须按照上述列表排列-->
<configuration><!--JDBC环境配置,选中默认环境--><environments default="dev"><!--Mysql数据库环境配置--><environment id="dev"><!--事务管理,这里的JDBC是一个类的别名:org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory--><transactionManager type="JDBC"/><!--连接池,这里的POOLED也是一个类的别名:org.apache.ibatis.datasource.pooled.PooledDataSourceFactory--><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&amp;tinyInt1isBit=false"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--Mapper注册--><mappers><!--注册Mapper文件的所在位置--></mappers>
</configuration>

创建userMapper接口以及接口的映射文件

userMapper:

public interface UserMapper {User getUserByUsername(String username);
}

userMapper.xml:

<!--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">
<!--namespace = 所需实现的接口全限定名-->
<mapper namespace="com.qf.mybatis.mapper.UserMapper"><!--id表示接口中的方法名,resultType表示查询结果每一行数据对应的转换类型--><select id="getUserByUsername" resultType="com.qf.mybatis.pojo.User"><!--#{arg0}表示获取方法参数列表中的第一个参数值--><!--#{param1}表示获取方法参数列表中的第一个参数值-->SELECT username,password,name,sex FROM user where username=#{arg0}</select>
</mapper>

注册Mapper接口

<mappers><!--注册Mapper文件的所在位置--><mapper resource="mapper/userMapper.xml"/></mappers>

测试

构建SqlSessionFactory的构建者,获取配置文件信息,根据配置文件信息构建SqlSessionFactory工厂,工厂开启sqlsession会话。

以上是程序性操作

然后从会话中获得接口的代理对象,底层是动态代理。

@Test
public void getUserByUserNameTest() throws IOException {//构建SqlSessionFactory的构建者SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//获取配置文件信息InputStream is = Resources.getResourceAsStream("config.xml");//根据配置信息构建工厂SqlSessionFactory factory = builder.build(is);//工厂开启sql会话SqlSession session = factory.openSession();//从会话中获得userMapper接口的代理对象(原理是动态代理)UserMapper userMapper = session.getMapper(UserMapper.class);//调用方法User user = userMapper.getUserByUsername("zs");System.out.println(user);
}

properties文件配置

Mybatis支持properties文件的引入,这样做的目的就是为了区分配置:不同的文件中描述不同的配置,这样方便管理。 在 resources 目录下新建 jdbc.properties 文件:

#jdbc.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/lesson?characterEncoding=utf8&tinyInt1isBit=false
jdbc.username=root
jdbc.password=root

,然后在 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">
<!--MyBatis配置-->
<configuration><!--引入jdbc.properties文件--><properties resource="jdbc.properties"/><!--JDBC环境配置,选中默认环境--><environments default="dev"><!--Mysql数据库环境配置--><environment id="dev"><!--事务管理,这里的JDBC是一个类的别名:org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory--><transactionManager type="JDBC"/><!--连接池,这里的POOLED也是一个类的别名:org.apache.ibatis.datasource.pooled.PooledDataSourceFactory--><dataSource type="POOLED">
<!--                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
<!--                <property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&amp;tinyInt1isBit=false"/>-->
<!--                <property name="username" value="root"/>-->
<!--                <property name="password" value="123456"/>--><property name="diver" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--Mapper注册--><mappers><!--注册Mapper文件的所在位置--><mapper resource="mapper/userMapper.xml"/></mappers>
</configuration>

类型别名

在Mapper接口映射文件userMapper.xml文件中

<!--id表示接口中的方法名,resultType表示查询结果每一行数据对应的转换类型--><select id="getUserByUsername" resultType="com.qf.mybatis.pojo.User">

resultType属性配置很繁琐,当方法很多的时候,开发效率大大降低,因此Mybatis提供了为类型定义别名的功能。该功能需要在config.xml中配置

<!--配置类型的别名:typeAlias方式和package方式只能选择其一-->
<typeAliases><!--        &lt;!&ndash;配置单个类的别名&ndash;&gt;--><!--        <typeAlias type="com.qf.mybatis.pojo.User" alias="user" />--><!--配置需要取别名的类的包,该包中所有类的别名均为类名--><package name="com.qf.mybatis.pojo"/>
</typeAliases>

userMapper.xml:

<select id="getUserByUsername" resultType="User">

日志配置

Mybatis本身有提供日志功能,开启日志需要在 config.xml 进行配置

<!-- 打印SQL语句 STDOUT_LOGGING是一个类的别名:org.apache.ibatis.logging.stdout.StdOutImpl--><setting name="logImpl" value="STDOUT_LOGGING"/>

注:常用配置标签的先后顺序
properties,
settings,
typeAliases,
typeHandlers,
plugins,
environments,
mappers
如果配置文件中同时存在这些配置标签,它们之间的顺序必须按照上述列表排列。

Mybatis增删改查

由于每次实现方法都需要构建SqlSessionFactory的构建者,获取配置文件信息,根据配置文件信息构建SqlSessionFactory工厂,工厂开启sqlsession会话。

因此把这部分封装起来作为工具类:

FactoryUtil:

public class FactoryUtil {private static SqlSessionFactory factory;static {//构建SqlSessionFactory的构建者SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//获取配置文件信息InputStream is = null;try {is = Resources.getResourceAsStream("config.xml");//根据配置信息构建工厂factory = builder.build(is);} catch (IOException e) {throw new RuntimeException(e);}}public static SqlSession getSqlSession() {return factory.openSession();}
}

标签

<select id="getUser"/>
<insert id="addUser"/>
<delete id="deleteUser"/>
<update id="updateUser"/>

参数取值

在Mybatis中,参数取值有两种方式:一种是#{表达式}, 另一种是 **${表达式} ** ;

#{表达式} 采用的是JDBC中的预编译来实现,因此可以防止SQL注入。

**${表达式} ** 采用的是字符串拼接,因此常用在排序字段变化、分组字段变化、查询表名变化等场景。

常用数据类型作为参数:

使用arg参数下标或者param参数位置获取参数

如:

User getUserByUsername(String username);
<select id="getUserByUsername" resultType="User">SELECT username,password,name,sex FROM user where username=#{arg0}
</select>
public void getUserByUserNameTest() throws IOException {SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.getUserByUsername("zs");System.out.println(user);
}
实体对象作为参数

使用#{属性名}获取对象属性参数

单个对象:

接口方法:

int addUser(User user);

xml映射:

<insert id="addUser">INSERT into user values (#{username},#{password},#{name},#{sex})
</insert>
public void addUser(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);User user = new User();user.setName("吉吉");user.setSex(1);user.setUsername("jj");user.setPassword("123456");int i = userMapper.addUser(user);try {session.commit();//不提交事务就不会对数据库中的数据进行修改} catch (Exception e) {session.rollback();//如果提交失败回滚事务}System.out.println(i);
}

多个对象:

int updateUserPassword(User user1,User user2);
<update id="updateUserPassword">update user set password=#{arg0.password} where username=#{arg1.username}
</update>
public void updateUser(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);User user1 = new User();User user2 = new User();user1.setUsername("zs");user2.setUsername("ls");user1.setPassword("123456");int i = userMapper.updateUserPassword(user1, user2);try {session.commit();} catch (Exception e) {session.rollback();}System.out.println(i);
}
Map作为参数:

由于Map中存放的数据是通过键值对实现的,因此可以将Map当做一个实体类对象来看待。Map中的键就相当于实体类中的属性名,Map中的值就相当于实体类中的属性值。因此,其取值方式与实体类对象作为参数一样。

int deleteUser(Map<String,Object> params);
<delete id="deleteUser">DELETE from user where username=#{username} and password=#{password}
</delete>
public void deleteUser(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);Map<String, Object> params = new HashMap<>();params.put("username","jj");params.put("password","123456");int i = userMapper.deleteUser(params);try {session.commit();} catch (Exception e) {session.rollback();}System.out.println(i);
}

参数注解

为了方便开发,Mybatis对参数提供了注解,从而可以给参数指定名称,方便在对应的Mapper映射文件中使用

List<User> retrieveUsers(@Param("condition")Map<String,Object> params);
<select id="retrieveUsers" resultType="User">select * from user where password=#{condition.password} and sex=#{condition.sex}</select>
public void retriveUsers(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);Map<String, Object> params = new HashMap<>();params.put("password","321321");params.put("sex",1);List<User> users = userMapper.retrieveUsers(params);try {session.commit();} catch (Exception e) {session.rollback();}users.forEach(System.out::println);}

主键回填

当保存一条数据时,我们需要该数据的ID,ID生成有两种方式:一种是数据库自动生成,一种是程序通过编码生成。Mybatis也提供了这两种方式来生成ID,ID生成后可以设置到给定的属性上,这个过程称之为主键回填。

一般采用数据库自动生成的ID,而不是程序编码生成的,因为程序生成的意义不大,无法从数据库中查询。

创建表:

-- 创建表
DROP TABLE IF EXISTS score;
CREATE TABLE score (-- 主键自增id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',name varchar(20) NOT NULL COMMENT '姓名',score double(5,2) DEFAULT NULL COMMENT '成绩'
) ENGINE=InnoDB CHARSET=UTF8;

对应实体类:

@Data
public class Score {private long id;private String name;private Double score;}

映射文件:

<?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.qf.mybatis.mapper.ScoreMapper"><insert id="addScore"><!-- selectKey表示选择键 通常都是用于主键回填功能 keyProperty表示回填的值设置到哪个属性上
resultType表示回填的值的数据类型  order表示主键回填的时机 AFTER表示数据保存后 BEFORE表示数据插入之前--><selectKey keyProperty="score.id" resultType="long" order="AFTER">SELECT LAST_INSERT_ID()</selectKey>INSERT INTO score(name,score)VALUES(#{score.name},#{score.score})</insert>
</mapper>

注册;

<mapper resource="mapper/scoreMapper.xml"/>

测试:

public void addScore(){SqlSession session = FactoryUtil.getSqlSession();ScoreMapper mapper = session.getMapper(ScoreMapper.class);Score score = new Score();score.setName("zs");score.setScore(99.0);int i = mapper.addScore(score);try {session.commit();} catch (Exception e) {session.rollback();}System.out.println(i);
}

关键点:

使用 SELECT LAST_INSERT_ID() 获取自动生成的主键值,并将其回填到 score 对象的 id 属性中

结果映射

在SQL查询时,我们经常会遇到数据库表中设计的字段名与对应的实体类中的属性名不匹配的情况,针对这种情况,Mybatis 提供了结果集映射,供用户自己实现数据库表中字段与实体类中属性进行匹配。

DROP TABLE IF EXISTS employee;
CREATE TABLE employee(id int NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '员工编号',name varchar(30) NOT NULL COMMENT '姓名',entry_time datetime NOT NULL COMMENT '入职时间',leave_time datetime DEFAULT NULL COMMENT '离职时间'
) ENGINE=InnoDB CHARSET=UTF8;
// 创建实体类 员工
public class Employee {private long id;private String name;private Date entryTime;private Date leaveTime;//省略getter和setter//构造方法:要么无参,要么全参
}// 创建Mapper接口
public interface EmployeeMapper {List<Employee> getAllEmployees();
}
<?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.qf.mybatis.mapper.EmployeeMapper"><resultMap id="empMap" type="com.qf.mybatis.model.Employee"><id property="id" column="id" /><result property="name" column="name" /><!--数据表中列名与实体类中的属性名匹配--><result property="entryTime" column="entry_time" /><!--数据表中列名与实体类中的属性名匹配--><result property="leaveTime" column="leave_time" /></resultMap><select id="getAllEmployees" resultMap="empMap">SELECT id,name,entry_time,leave_time FROM employee</select>
</mapper>

也可以直接对表中字段重命名。

Mybatis级联查询

1. 一对一级联查询

创建签证表和乘客表,其中一个乘客有多个签证,而一个签证只对应一个乘客:

DROP TABLE IF EXISTS passenger;
CREATE TABLE passenger (id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '乘客编号',name varchar(50) NOT NULL COMMENT '姓名',sex tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别',birthday date NOT NULL COMMENT '生日'
) ENGINE=InnoDB CHARSET=UTF8;DROP TABLE IF EXISTS passport;
CREATE TABLE passport (id bigint NOT NULL AUTO_INCREMENT COMMENT '护照编号',office varchar(50) NOT NULL COMMENT '签证机关',valid_time tinyint NOT NULL COMMENT '有效期限',nationality varchar(50) NOT NULL COMMENT '国籍',passenger_id bigint NOT NULL COMMENT '乘客编号',PRIMARY KEY (id),FOREIGN KEY (passenger_id) REFERENCES passenger (id)
) ENGINE=InnoDB CHARSET=UTF8;

创建对应的实体类,属性名采用驼峰命名法:

@Data
public class Passenger {private long id;private String name;private int sex;private Date birthday;}//----------------------------------------------------
@Data
public class Passport {private long id;private String nationality;private int validTime;private String office;private Passenger passenger;
}

现在要通过查询签证表的同时查询出乘客表

因此要写是PassportMapper接口,其中的方法为获取所有签证对象

public interface PassportMapper {List<Passport> getAllPassports();
}

然后写映射文件,因为是通过查询签证表查到乘客表的一对一级联,所以只用写签证表的映射文件。

方式一:

签证表的passenger字段和乘客表的id字段由一个参数passengerId进行连接,先查passport表,然后将参数作为索引查passenger表.

因此要写PassengerMapper接口,其中的方法为获取所有乘客对象。

public interface PassengerMapper {List<Passenger> getPassengers();
}

映射文件:

<?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.qf.mybatis.mapper.PassportMapper"><resultMap id="passportMap" type="Passport"><!--查询单张表可以只写实体类属性和数据库字段名不同的部分--><result property="validTime" column="valid_time"/><!--一对一的级联查询使用的是association标签--><!--级联查询也支持传递参数,传递参数需要通过column属性来传递,定义参数的语法:{参数名=列名,...,参数名n=列名n}--><association property="passenger" column="{passengerId = passenger_id}" select="getPassengers"/></resultMap><select id="getAllPassports" resultMap="passportMap">select * from passport</select><select id="getPassengers" resultType="Passenger">select * from passenger where id=#{passengerId}</select>
</mapper>

方式二:

将两个表连接起来,查询连接后的表:

注意:在查询复杂关系的表的时候需要在结果映射中将所有属性和数据字段名都写出来

<mapper namespace="com.qf.mybatis.mapper.PassportMapper"><resultMap id="passportMap" type="Passport"><id column="id" property="id" /><result column="nationality" property="nationality" /><result column="office" property="office" /><result column="valid_time" property="validTime" /><association property="passenger" javaType="passenger"><id column="passengerId" property="id" /><result column="name" property="name" /><result column="sex" property="sex" /><result column="birthday" property="birthday" /></association></resultMap><select id="getAllPassports" resultMap="passportMap">select a.id,a.nationality,a.office,a.valid_time,b.id passengerId,b.name,b.sex,b.birthday from passport a inner join passenger b on a.passenger_id = b.id</select>
</mapper>

2. 一对多级联查询

association改为collection

创建班级表和学生表:

DROP TABLE IF EXISTS class;
CREATE TABLE class (id int NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '班级编号',name varchar(50) NOT NULL COMMENT '名称'
) ENGINE=InnoDB CHARSET=UTF8;DROP TABLE IF EXISTS student;
CREATE TABLE student (id bigint NOT NULL AUTO_INCREMENT COMMENT '学号',name varchar(50) NOT NULL COMMENT '姓名',class_id int NOT NULL COMMENT '班级编号',PRIMARY KEY (id),FOREIGN KEY (class_id) REFERENCES class (id)
) ENGINE=InnoDB CHARSET=UTF8;
public class Student {private long id;private String name;
}
public class Clazz {private int id;private String name;private List<Student> students; //集合作为属性
}public interface ClazzMapper {List<Clazz> getClazzList();
}

方式一:查询两次

因此还需要StudentMapper接口:

public interface StudentMapper {List<Student> getStudents();
}

映射文件:

<mapper namespace="com.qf.mybatis.mapper.ClazzMapper"><resultMap id="clazzMap" type="Clazz"><id property="id" column="id"/><result property="name" column="name"/><!--一对多 级联 方式--><collection property="students" select="getStudents" column="{sid=id}"/></resultMap><select id="getClazzList" resultMap="clazzMap">select * from class</select><select id="getStudents" resultType="Student">select * from student where class_id = #{sid}</select></mapper>

方式二:

查询两个表的连接表:

<mapper namespace="com.qf.mybatis.mapper.ClazzMapper"><resultMap id="clazzMap" type="Clazz"><id column="id" property="id"/><result column="name" property="name"/><collection property="students" ofType="Student"><id column="sid" property="id"/><result column="sname" property="name"/></collection></resultMap><select id="getClazzList" resultMap="clazzMap">selecta.id,a.name,b.id sid,b.name snamefrom class a inner join student bon a.id=b.class_id</select>
</mapper>

注意:重复的名字需要重命名。

3.RBAC权限模型查询

RBAC权限模型介绍

RBAC(Role Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联,而不是直接将权限赋予用户。

如现在有以下表:

数据表:
用户表
username varchar(50) primary key
password varchar(200)
name varchar(50)角色表
id int(11) primary key auto_increment
name varchar(50)用户角色表
username varchar(50)
role_id int(11)菜单表
id int(11) primary key auto_increment
name varchar(50)
parent_id int(11)角色菜单表
role_id int(11)
menu_id int(11)

关系如下:

在这里插入图片描述

RBAC模型中,用户与角色之间、角色与权限之间,一般是多对多的关系。

现在有一个需求,根据用户查询到对应的菜单。

这里采用非级联查询和级联查询(均采用查询一次的方式)

实体类:

@Data
public class User {private String username;private String password;private String name;private List<Menu> menus;
}
//----------------------------------
@Data
public class Menu {private int id;private String name;
}
非级联查询

接口方法:

List<Menu> getMenus(String username);

映射文件:

<resultMap id="menuMap" type="Menu"><id column="mid" property="id"/><result column="mname" property="name"/>
</resultMap>
<select id="getMenus" resultMap="menuMap">select m.id mid,m.name mname from menu m join role_menu rm on m.id=rm.menu_idjoin roles r on rm.role_id=r.idjoin user_role ur on r.id=ur.role_idjoin user u on ur.username = u.usernamewhere u.username=#{username}
</select>

测试:

@Test
public void getMenusByUsername() throws IOException {SqlSession session = getSession();UserMapper mapper = session.getMapper(UserMapper.class);List<Menu> menus = mapper.getMenus("jj");try {session.commit();} catch (Exception e) {session.rollback();}menus.forEach(System.out::println);
}private SqlSession getSession() throws IOException {SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();InputStream is = Resources.getResourceAsStream("config.xml");SqlSessionFactory factory = builder.build(is);SqlSession session = factory.openSession();return session;
}
级联查询

接口方法:

List<User> getUsers();

映射文件:

<resultMap id="userMap" type="User"><id property="username" column="username"/><result property="password" column="password"/><result property="name" column="uname"/><result property="sex" column="sex"/><collection property="menus" ofType="Menu"><id property="id" column="id"/><result property="name" column="mname"/><result property="parentId" column="parent_id"/></collection>
</resultMap><select id="getUsers" resultMap="userMap">select u.username,u.password,u.name uname,m.id,m.name mnamefrom user uleft join user_role ur on u.username = ur.usernameleft join roles r on ur.role_id = r.idleft join role_menu rm on r.id = rm.role_idleft join menu m on rm.menu_id = m.parent_id
</select>

测试:

@Test
public void getMenusByUser() throws IOException {SqlSession session = getSession();UserMapper mapper = session.getMapper(UserMapper.class);List<User> users = mapper.getUsers();try {session.commit();} catch (Exception e) {session.rollback();}users.forEach(System.out::println);
}private SqlSession getSession() throws IOException {SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();InputStream is = Resources.getResourceAsStream("config.xml");SqlSessionFactory factory = builder.build(is);SqlSession session = factory.openSession();return session;
}
级联查询与非级联查询的区别

级联查询查到的结果是包含其他类的集合作为属性的类,这里就是User,User中含有menu的集合属性,因此sql查询中查询的目标含有user表外的其他表的字段内容,且这些表之间有连接关系。而非级联查询只能查询当前表中的内容,返回的是查询对象的类。

动态SQL

sql标签

将特定的SQL代码封装起来,方便进行重用

<!--多条SQL都会使用的字段可以使用sql标签来定义,使用时通过include标签来引入-->
<sql id="fields">username,password,name
</sql>
<select id="getUser" resultType="User">select <include refid="fields"/> from user where username=#{username}
</select>

if标签

满足标签的验证内容时才将标签内的内容拼接至sql语句中

<!--if标签-->
<!--直接在sql语句中插入-->
<select id="getUserList" resultType="User">select * from user where 1=1<if test="conditions.name!=null and conditions.name!=''">and name like concat('%',#{conditions.name},'%')</if>
</select>

where标签

代替sql语句中的where,可以与if联合使用。当 where 标签内存在查询条件时, where 标签会在SQL代码中添加 WHERE 关键字; 当 where 标签内不不存在查询条件时, where 标签将忽略 WHERE 关键字的添加。除此之外,where 标签还将自动忽略其后的 AND 或者 OR 关键字。

<select id="getUserList" resultType="User">select * from user<!--where标签,会自动添加where并忽略后面的and或者or关键字--><where><if test="conditions.name!=null and conditions.name!=''">and name like concat('%',#{conditions.name},'%')</if></where>
</select>

set标签

代替sql语句中的update xxx set这里的set,实现动态更新。

set标签会忽略最后一个sql子句的后缀,比如逗号。

<update id="updateUserPassword">update user<set><if test="conditions.password!=null and conditions.password!=''">password = #{conditions.password}</if><where><if test="conditions.username!=null and conditions.username!='' ">and username = #{conditions.username}</if></where></set>
</update>

trim标签

Mybatis 提供了 trim 标签来代替 where 标签和 set 标签。

<!-- 其中 prefixOverrides 属性表示要被重写的前缀,prefix 属性表示用来替换重写的前缀内容。suffix和suffixOvverdides 属性表示对后缀的处理-->
<trim prefix="" prefixOverrides="" suffix="" suffixOverrides=""></trim>
<select id="getScores" resultType="score">SELECT id,name,score FROM score<trim prefix="WHERE" prefixOverrides="AND"><if test="params.name != null and params.name != ''">AND name LIKE CONCAT('%', #{params.name}, '%')</if><if test="params.scoreFrom != null and params.scoreFrom != ''">AND score >= #{params.scoreFrom}</if><if test="params.scoreTo != null and params.scoreTo != ''"><![CDATA[AND score <= #{params.scoreTo}]]></if></trim>
</select><update id="updateScore">UPDATE score<trim suffixOverrides="," suffix=""><if test="s.name != null and s.name != ''">name = #{s.name},</if><if test="s.score != null and s.score != ''">score = #{s.score},</if></trim><where><if test="s.id != null and s.id != ''">AND id = #{s.id}</if></where>
</update>

foreach标签

collection表示遍历的元素类型,如果参数没有使用注解命名,那么该属性值只能是list,array,map其中之一;如果参数使用了注解命名,那么该属性值直接使用注解指定的名称即可。
item表示每次遍历时使用的对象名
open表示前面添加的内容
close表示最后添加的内容
seperator表示每次遍历时内容组装使用的分割符
index表示遍历时的下标

<foreach collection="" item="" open="" seperator="" close="" index=""></foreach>

例:

<delete id="deleteUserByUsername">delete from user where username in<foreach collection="usernames" item="username" open="(" separator="," close=")">#{username}</foreach>
</delete>

Mybatis缓存

什么是缓存?

缓存是存储在内存中的临时数据,将用户经常查询的数据放在缓存(内存)中,用户再次查询数据的时候就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,能够提高查询效率,解决了高并发系统的性能问题。

为什么使用缓存?

减少和数据库的交互次数,提高效率

缓存的对象

经常查询并且很少改变的数据

一级缓存(没用)

又名Session缓存,简单地说,整个缓存的管理都由Session完成,开发者不需要做任何的事情,这个缓存本身就存在,但是这个一级缓存不能跨越Session,所以没用。

public void getUserByUserNameTest() throws IOException {SqlSession session = FactoryUtil.getSqlSession();//从会话中获得userMapper接口的代理对象(原理是动态代理)UserMapper userMapper = session.getMapper(UserMapper.class);//调用方法userMapper.getUserByUsername("zs");userMapper.getUserByUsername("zs");//清空session中的缓存session.clearCache();userMapper.getUserByUsername("zs");userMapper.getUserByUsername("zs");//        System.out.println(user);
}

在这个测试中,日志中只会打印出两遍sql语句,第一遍是第一次调用方法进行查询的时候,使用sql语句后session会利用一级缓存将查询结果保存,因此再次查询不会再次用sql去查。第二次是由于清空了session中的缓存,所以会重新去查询。

二级缓存

能跨越session,可以使用mybatis默认的简单的二级缓存( 一个简单的、非持久化的内存缓存),也可以引入外部缓存库。

使用外部缓存库:

导入ehcache-core包和mybatis-ehcache包(这是个中间包,承上启下,用于整合ehcache框架和mybatis.cache框架)。

 <dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.11</version></dependency><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version></dependency>

创建ehcache.xml文件,不用记,只需要根据官方文档改数据就行。这里需要改diskStore中数据在硬盘上的存储位置。

<ehcache><diskStore path="java.io.tmpdir"/><cache name="com.example.MyMapper"maxEntriesLocalHeap="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"></cache>
</ehcache>

然后再配置文件中的settings中需要开启二级缓存

<sesstings name="cacheEnabled" value="true"/>

哪个Mapper.xml的查询中需要使用二级缓存就在哪里进行配置

<cache type="org.mybatis.caches.EhcacheCache"></cache>
使用默认的二级缓存:

不用导入依赖,全局缓存相同:

<sesstings name="cacheEnabled" value="true"/>

在mapper.xml中:

<!-- cache标签表示使用缓存flushInterval:表示缓存刷新时间,单位是毫秒readyOnly:表示是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改size:表示存放多少条数据eviction: 缓存回收策略,有这几种回收策略LRU - 最近最少回收,移除最长时间不被使用的对象FIFO - 先进先出,按照缓存进入的顺序来移除它们SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象-->
<cache flushInterval="300000" readOnly="true" size="10000" eviction="LRU"/>
测试:
@Test
public void getUserByUserNameTest() throws IOException {SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);userMapper.getUserByUsername("zs");//需要提交并关闭才能进入二级缓存session.commit();session.close();//------------------------------------------------SqlSession session1 = FactoryUtil.getSqlSession();UserMapper userMapper1 = session1.getMapper(UserMapper.class);userMapper1.getUserByUsername("zs");session1.commit();session1.close();//这时日志中只有一次sql语句
}

注意: 二级缓存失效

二级缓存缓存数据的前提是查询的 SqlSession 关闭,如果 SqlSession 没有关闭,那么数据将不会进入二级缓存,再次进行同构查询时,二级缓存由于没有数据,查询将进入数据库,造成二级缓存失效的现象。

另一种情况是,当前查询的 SqlSession 已经关闭,数据也进入了二级缓存,但在下一次查询之前,如果中间发生了更新操作,该操作更新的数据在的二级缓存中存在,那么二级缓存也将失效。

分页插件 PageHelper

Mybatis中的拦截器:

MyBatis的拦截器可以拦截Executor、ParameterHandler、ResultSetHandler和StatementHandler这四种类型的方法。

1.Executor:负责执行SQL语句,是MyBatis中最核心的组件之一。它负责管理缓存、执行SQL语句、处理缓存中的数据等。

2.ParameterHandler:负责处理SQL语句中的参数,将Java对象转换为JDBC Statement所需的参数。

3.ResultSetHandler:负责处理SQL查询结果集,将JDBC返回的ResultSet对象转换为Java对象。

4.StatementHandler:负责处理SQL语句的生成和执行,包括SQL语句的预编译、参数设置等操作。

这个插件本质上也是一个拦截器,要实现分页,就可以拦截Executor中的Query方法,然后取出这个SQL语句,取出表名,通过表名构建统计的SQL语句 **select count(*) from 表名,**于是向数据库发送一次请求,拿到数据的条目数 total,然后取出要查询的页码和每一页数据的个数,计算应该从哪里查询,查询多少条数据,于是构建第二个SQL语句,**原来的SQL语句 limit 查询的开始位置,查询的条目数,**得到查询数据的结果,然后将total和数据封装到一个类中进行数据的返回…

使用

导入分页插件的包pagehelper

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.0</version>
</dependency>

配置文件中进行分页插件配置plugins

<!-- config.xml中进行配置 -->
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

测试

@Test
public void getAllUsers(){SqlSession session = FactoryUtil.getSqlSession();UserMapper mapper = session.getMapper(UserMapper.class);PageHelper.startPage(2,3);//查询第二页,每页三条数据,这句必须在查询前!List<User> allUsers = mapper.getAllUsers();PageInfo<User> pageInfo = new PageInfo<>(allUsers);//将查询结果保存到PageInfo对象中System.out.println("总条数:"+ pageInfo.getTotal());System.out.println("总页数" + pageInfo.getPages());pageInfo.getList().forEach(System.out::println);//展示查询结果try {session.commit();} catch (Exception e) {session.rollback();} finally {session.close();}
}

注意:设置查询页码和每页条数的语句必须在调用查询方法之前,否则查询的结果将不会实现分页效果。

配置数据源 Druid

Druid 是阿里巴巴开源平台上的一个项目,是性能最好的数据库连接池,如何在Mybatis中配置该数据源呢?

<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version>
</dependency>

创建 DruidDataSourceFactory, 并继承 PooledDataSourceFactory,并替换数据源

public class DruidDataSourceFactory extends PooledDataSourceFactory {public DruidDataSourceFactory() {this.dataSource = new DruidDataSource();//替换数据源}
}
<!--config.xml-->
<dataSource type="com.qf.mybatis.datasource.DruidSourceFactory"><!--                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>--><!--                <property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&amp;tinyInt1isBit=false"/>--><!--                <property name="username" value="root"/>--><!--                <property name="password" value="123456"/>--><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource>

注意: 在 Druid 数据源中,属性名称是 driverClassName,而不是 driver。因此,需要使用 driverClassName 进行配置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/41991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

imx6ull/linux应用编程学习(14) MQTT基础知识

什么是mqtt&#xff1f; 与HTTP 协议一样&#xff0c; MQTT 协议也是应用层协议&#xff0c;工作在 TCP/IP 四层模型中的最上层&#xff08;应用层&#xff09;&#xff0c;构建于 TCP/IP协议上。 MQTT 最大优点在于&#xff0c;可以以极少的代码和有限的带宽&#xff0c;为连接…

网络资源模板--Android Studio 外卖点餐App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 原创外卖点餐&#xff1a;基于Android studio 实现外卖(点)订餐系统 非原创奶茶点餐&#xff1a;网络资源模板--基于 Android Studio 实现的奶茶点餐App报告 一、项目演示 网络资源模板--基于Android …

在AvaotaA1全志T527开发板上使用AvaotaOS 部署 Docker 服务

Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口。 准备…

信息技术课上的纪律秘诀:营造有序学习环境

信息技术课是学生们探索数字世界的乐园&#xff0c;但同时也是课堂纪律管理的挑战场。电脑、网络、游戏等元素可能分散学生的注意力&#xff0c;影响学习效果。本文将分享一些有效的策略&#xff0c;帮助教师在信息技术课上维持课堂纪律&#xff0c;确保教学活动顺利进行。 制…

几何建模基础-样条曲线和样条曲面介绍

1.概念介绍 1.1 样条曲线的来源 样条的英语单词spline来源于可变形的样条工具&#xff0c;那是一种在造船和工程制图时用来画出光滑形状的工具&#xff1a;富有弹性的均匀细木条/金属条/有机玻璃条&#xff0c;它围绕着按指定位置放置的重物或者压铁做弹性弯曲&#xff0c;以…

基于LangChain的RAG开发教程(二)

v1.0官方文档&#xff1a;https://python.langchain.com/v0.1/docs/get_started/introduction/ 最新文档&#xff1a;https://python.langchain.com/v0.2/docs/introduction/ LangChain是一个能够利用大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;能…

植物大战僵尸融合嫁接版 MAC 版本下载安装详细教程

继植物大战僵尸杂交版火了之后&#xff0c;PVZ改版可谓是百花齐放&#xff0c;最近又有一个非常好玩的模式被开发出来了&#xff0c;他们称为《植物大战僵尸融合嫁接版》 该版本并没有对植物卡牌做改动&#xff0c;而是可以将任意两种植物叠放到一起进行融合&#xff0c;产生新…

思路打开!腾讯造了10亿个角色,驱动数据合成!7B模型效果打爆了

世界由形形色色的角色构成&#xff0c;每个角色都拥有独特的知识、经验、兴趣、个性和职业&#xff0c;他们共同制造了丰富多元的知识与文化。 所谓术业有专攻&#xff0c;比如AI科学家专注于构建LLMs,医务工作者们共建庞大的医学知识库&#xff0c;数学家们则偏爱数学公式与定…

数据分析与挖掘实战案例-电商产品评论数据情感分析

数据分析与挖掘实战案例-电商产品评论数据情感分析 文章目录 数据分析与挖掘实战案例-电商产品评论数据情感分析1. 背景与挖掘目标2. 分析方法与过程2.1 评论预处理1. 评论去重2. 数据清洗 2.2 评论分词1. 分词、词性标注、去除停用词2. 提取含名词的评论3. 绘制词云查看分词效…

昇思25天学习打卡营第12天 | LLM原理和实践:MindNLP ChatGLM-6B StreamChat

1. MindNLP ChatGLM-6B StreamChat 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。 ChatGLM-6B应该是国内第一个发布的可以在消费级显卡上进行推理部署的国产开源大模型&#xff0c;2023年3月就发布了。我在23年6月份的时候就在自己的笔记本电脑上部署测试过&#xff0c;当…

UI自动化测试框架:PO 模式+数据驱动(超详细)

1. PO 设计模式简介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成一个 Page 类&#xff0c;并以页面为单位来写测试用例&#xff0c;实现页面对象和测试用例的分离。 PO 模式的设计思想与…

Python学习中进行条件判断(if, else, elif)

条件判断是编程中必不可少的一部分&#xff0c;它让程序可以根据不同的条件执行不同的代码块。在Python中&#xff0c;主要使用if、elif和else语句来实现条件判断。 基本语法 在Python中&#xff0c;条件判断的基本语法如下&#xff1a; if condition:# 当condition为True时…

NCCL 中的一些辅助debug 知识点

1&#xff0c;调试nccl 启动kernel的方法 ncclLaunchKernel cuLaunchKernelEx ncclStrongStreamLaunchKernel cudaLaunchKernel ncclLaunchOneRank cudaLaunchKernel 在 nccl lib 中&#xff0c;不存在使用<<<grid, block,,>>> 这种类似方式启…

算法题型归类整理及同类题型解法思路总结(持续更新)

1、最优路线 通用思路 1、递归 #案例1-最优路测路线 题目描述 评估一个网络的信号质量&#xff0c;其中一个做法是将网络划分为栅格&#xff0c;然后对每个栅格的信号质量计算。 路测的时候&#xff0c;希望选择一条信号最好的路线&#xff08;彼此相连的栅格集合&#x…

12种增强Python代码的函数式编程技术

前言 什么是函数式编程&#xff1f; 一句话总结&#xff1a;函数式编程(functional programming)是一种编程范式&#xff0c;之外还有面向对象&#xff08;OOP&#xff09;、面向过程、逻辑式编程等。 函数式编程是一种高度抽象的编程范式&#xff0c;它倡导使用纯函数&#x…

Docker-11☆ Docker Compose部署RuoYi-Cloud

一、环境准备 1.安装Docker 附:Docker-02-01☆ Docker在线下载安装与配置(linux) 2.安装Docker Compose 附:Docker-10☆ Docker Compose 二、源码下载 若依官网:RuoYi 若依官方网站 鼠标放到"源码地址"上,点击"RuoYi-Cloud 微服务版"。 跳转至G…

深入理解计算机系统 CSAPP 家庭作业8.22

书本知识够你写出答案,但是如果你想验证你写的答案,就要一些额外的东西.这本书很多题目都是如此 /** mysystem.c*/ #include <stdio.h> #include "csapp.h"int mysystem(char* command) {pid_t pid;int status;if ((pid Fork()) 0) {/*这里是关键用子程序去…

新加坡工作和生活指北:工作篇

文章首发于公众号&#xff1a;Keegan小钢 一年多以前&#xff08;2022 年 8 月初&#xff09;&#xff0c;那时我过来新加坡才 4 个多月&#xff0c;就写了篇文章分享了当时在新加坡的生活和工作体验。文章得到的反响不错&#xff0c;但也反馈出了一些新的问题&#xff0c;比如…

预训练对齐:数学理论到工程实践的桥梁

在人工智能和机器学习领域&#xff0c;预训练模型的对齐是一个至关重要的概念。本篇博客源自听了一场黄民烈老师关于大模型对齐的分享&#xff0c;整理内容如下&#xff0c;供大家参考。 数学理论中的预训练对齐 数学理论上&#xff0c;预训练对齐是什么&#xff1f; 序列…

Keepalived+HAProxy 集群及虚IP切换实践

1、软件介绍 ①Keepalived keepalive是一个用c语言编写的路由软件&#xff0c;这个项目的主要目标是为Linux系统和基于Linux的基础设施提供简单而健壮的负载平衡和高可用性设施。负载均衡框架依赖于众所周知且广泛使用的Linux Virtual Server (IPVS)内核模块提供第4层负载均衡…