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&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&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><!-- <!–配置单个类的别名–>--><!-- <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&tinyInt1isBit=false"/>--><!-- <property name="username" value="root"/>--><!-- <property name="password" value="123456"/>--><property name="driverClass" value="${jdbc.driverClassName}"/><property name="jdbcUrl" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource>
注意: 在 Druid 数据源中,属性名称是
driverClass和jdbcUrl,而不是driver和url。因此,需要使用driverClassName进行配置。
相关文章:
MyBatis的简介与使用
Mybatis JDBC操作数据库的缺点 存在大量的冗余代码。手工创建 Connection、Statement 等,效率低下。手工将结果集封装成实体对象。查询效率低,没有对数据访问进行优化。 Mybatis框架 简介 MyBatis 本是 apache 的一个开源项目 iBatis, 2010年这个项目由…...
MAX98357、MAX98357A、MAX98357B小巧、低成本、PCM D类IIS放大器,具有AB类性能中文说明规格书
前言: MAX98357A支持标准I2S数据,MAX98357B支持左对齐数字音频数据。两个版本均支持8通道TDM音频数据。 IIS数字功放MAX98357开发板/评估系统 MAX98357 WLP-9(1.347x1.437mm)封装的外观和丝印AKM MAX98357 TQFN-16-EP(3x3mm)封装的外观和丝印AKK 引脚说…...
shell(2)
shell(2) 简答题 1、编写一个shell脚本,从键盘读入一个成绩,并按优秀、良好、中等、及格、不及格输出成绩。 我的答案: #/bin/bash read -p "请输入学生成绩(0-100):" score if [ $sum -gt 100 ] ;thenecho "输…...
昇思25天学习打卡营第1天|初识MindSpore
昇思MindSpore介绍 昇思MindSpore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景统一部署三大目标。 其中,易开发表现为API友好、调试难度低;高效执行包括计算效率、数据预处理效率和分布式训练效率;全场景则指框架…...
C语言字节对齐技术在嵌入式、网络与操作系统中的应用与优化
第一部分:嵌入式系统中的字节对齐 嵌入式系统通常对性能和资源有着严格的要求。在这些系统中,字节对齐的正确使用可以显著提高数据访问速度,减少内存占用,并提高系统的整体效率。 一、嵌入式系统中的字节对齐挑战 嵌入式系统中…...
如何理解李彦宏说的”不要卷模型,要卷应用
文章目录 👿AI技术的发展与转变👿不要卷模型,要卷应用👿避免“超级应用陷阱”👿大模型技术与个性化应用的关系👿结语 在2024年7月4日于上海世博中心举办的世界人工智能大会上,百度创始人、董事长…...
三、Python日志系统之监控邮件发送
import smtplib from email.mime.text import MIMEText import time import os import datetime from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler# 邮件配置 SMTP_SERVER smtp.example.com SMTP_PORT 587 SMTP_USERNAME your_…...
16张支付牌照将到期,新规落地以来,支付牌照的首次续展。
7月9日,包括瑞银信、乐刷、畅捷支付在内的16家第三方支付机构的支付牌照将到期,这些公司面临续展的重大考验。 这是《非银行支付机构监督管理条例》实施以来,支付牌照的首次续期。 其中,最受瞩目的可能是瑞银信。在范一飞落马后&a…...
VS2022 python 中文注释报错如何解决?
1. 相同的代码,在VS2022 中报错; # 初始化字典 my_dict {apple: 3, banana: 5} # 遍历字典 for index, key in enumerate(my_dict): print(f"index {index}, key {key} , value {key}") 在PyCharm Community Edition 2024.1…...
GitLab介绍,以及add an SSH key
GitLab GitLab 是一个用于仓库管理系统的开源项目,现今并在国内外大中型互联网公司广泛使用。 git,gitlab,github区别 git 是一种基于命令的版本控制系统,全命令操作,没有可视化界面; gitlab 是一个基于git实现的在线代码仓库…...
计算机视觉——opencv快速入门(二) 图像的基本操作
前言 上一篇文章中我们介绍了如何配置opencv,而在这篇文章我们主要介绍的是如何使用opencv来是实现一些常见的图像操作。 图像的读取,显示与存储 读取图像文件 在opencv中我们利用imread函数来读取图像文件,函数语法如下: imagecv2.imre…...
ViewPager
作用 实现翻页的效果。 1、在activity_main.xml中创建ViewPager <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"…...
linux watchdog 子系统
目录 一、watchdog 子系统二、关键数据结构2.1 watchdog_device2.2 watchdog_ops2.3 watchdog_info 三、重要流程3.1 watchdog 初始化3.2 watchdog 设备注册3.3 watchdog 设备文件操作函数3.4 watchdog 喂狗用户空间 watchdog(busybox)内核空间喂狗疑问 …...
论文引用h指数
文章目录 1、描述2、关键字3、思路4、notes5、复杂度6、code 1、描述 给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义:h 代表“高引用次数” &…...
四、Python日志系统之日志文件的备份和删除
import os import datetime import logging from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer """实现代码中处理日志文件的备份和删除""" class UserLog:def __init__(self):self.logger logging…...
Android Camera Framework:从基础到高级
目录 基础知识1. Camera API 与 Camera2 API2. 权限 关键组件1. CameraManager2. CameraDevice3. CaptureRequest 和 CaptureSession 高级功能1. 实时滤镜2. 手动控制3. 高动态范围 (HDR) 和夜间模式 在现代移动应用开发中,相机功能已成为许多应用程序的核心组成部分…...
面向 Rust 新手的 Cargo 教程:轻松上手
面向 Rust 新手的 Cargo 教程:轻松上手 引言Cargo 简介定义与重要性与传统构建工具的对比 安装与配置 Cargo在不同操作系统上安装 Rust 和 CargoWindowsmacOSLinux 配置 Cargo 环境变量与路径第一个 Cargo 项目 主要功能概述结语 引言 在编程语言的浩瀚宇宙中&…...
MSPM0G3507——时钟配置(与32关系)
先将32端时钟配置分为1,2,3如图 1是PSC左边未经分频的时钟源(HZ) 2是经过PSC分频的时钟信号(HZ) 3是最终的输出信号(HZ) 3输出的是一个定时器周期的HZ,可以转换成时间 …...
Linux 创建新虚拟机的全过程图解
一、创建新虚拟机 1.选择自定义 2.直接下一步 3.选择稍后安装 4.设置虚拟机名和安装位置 5.配置处理器(处理器数量:4、每个处理器的内核:2) 6. 内存选择 7.网络类型 8. IO控制器类型-默认推荐 9.磁盘类型-默认推荐 10.选择虚拟磁…...
【已解决】腾讯云安装了redis,但是本地访问不到,连接不上
汇总了我踩过的所有问题。 查看配置文件redis.conf 1、把bind 127.0.0.1给注释掉(前面加个#就是)或者改成bind 0.0.0.0,因为刚下载时它是默认只让本地访问。(linux查找文档里的内容可以输入/后面加需要匹配的内容,然后…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
