一、JDBC
1.JDBC介绍
JDBC全称Java DataBase Connectivity,即Java数据库连接,集成在JDK中,提供基础的数据库访问API。
2.JDBC使用
/**
* 通过JDBC查询用户信息
*/
public void queryUser(Integer id) {
Connection conn = null;
Statement stmt = null;
try {
// 1.获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/baiTest?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false", "root", "123456");
// 2.创建statement
stmt = conn.createStatement();
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = " + id;
// 3.执行脚本
ResultSet rs = stmt.executeQuery(sql);
// 4.获取结果集
while (rs.next()) {
User user = new User();
Integer uId = rs.getInt("id");
String userName = rs.getString("user_name");
String realName = rs.getString("real_name");
Integer age = rs.getInt("age");
String password = rs.getString("password");
Integer did = rs.getInt("d_id");
user.setId(uId);
user.setUserName(userName);
user.setRealName(realName);
user.setAge(age);
user.setPassword(password);
user.setDId(did);
System.out.println(user);
}
//5.关闭资源
rs.close();
stmt.close();
conn.close();
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException se2) {
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException se) {
se.printStackTrace();
}
}
}
- getConnection创建连接;
- 创建statement;
- 调用excute()执行sql语句;
- 获取到ResultSet结果集,然后给pojo赋值;
- 关闭资源
显然傻瓜式的操作存在诸多弊端 :代码重复、资源管理、pojo对象映射、sql耦合
二、Spring JDBC
在Spring框架平台下,也提供的有JDBC的封装操作,在Spring中提供了一个模板方法JdbcTemplate,里面封装了各种各样的 execute,query和update方法。
Instances of the JdbcTemplate class are thread-safe, once configured. This is important because it means that you can configure a single instance of a JdbcTemplate and then safely inject this shared reference into multiple DAOs (or repositories). The JdbcTemplate is stateful, in that it maintains a reference to a DataSource, but this state is not conversational state.
JdbcTemplate这个类是JDBC的核心包的中心类,简化了JDBC的操作,可以避免常见的异常,它封装了JDBC的核心流程,应用只要提供SQL语句,提取结果集就可以了,这玩意线程安全,记住这点很重要。
步骤简化了不少:
- 配置数据源,resource下新建druid.properties
- 注入template
@Configuration
@ComponentScan
public class SpringConfig {
@Bean
public DataSource dataSource() {
Properties properties = new Properties();
//获取数据源配置
InputStream in = SpringConfig.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
//使用Druid连接(也可以用MysqlDataSource替换)
DruidDataSource dataSource = new DruidDataSource();
dataSource.configFromPropety(properties);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate template = new JdbcTemplate();
template.setDataSource(dataSource);
return template;
}
}
三、Mybatis概览
1.什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。抄自–https://mybatis.org/mybatis-3/zh/index.html
2.前世今生
原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
3.mybatis架构
3.1 接口层
首先接口层是我们打交道最多的。核心对象是SqlSession,它是上层应用和MyBatis打交道的桥梁,SqlSession上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。
3.2 核心处理层
接下来是核心处理层。既然叫核心处理层,也就是跟数据库操作相关的动作都是在这一层完成的。核心处理层主要做了这几件事:
- 把接口中传入的参数解析并且映射成JDBC类型;
- 解析xml文件中的SQL语句,包括插入参数,和动态SQL的生成;
- 执行SQL语句;
- 处理结果集,并映射成Java对象。插件也属于核心层,这是由它的工作方式和拦截的对象决定的。
3.3 基础支持层
最后一个就是基础支持层。基础支持层主要是一些抽取出来的通用的功能(实现复用),用来支持核心处理层的功能。比如数据源、缓存、日志、xml解析、反射、IO、事务等等这些功能
四、Mybatis应用
1.编译环境
1.1 下载源码
git clone https://github.com/mybatis/parent
git clone https://github.com/mybatis/mybatis-3
1.2 编译打包
-
用idea分别对parent和mybatis-3打包
-
对mybatis打包为了辨识可以在版本version上加snapshot
-
这样本地仓库中就有了mybatis/3.5.4-snapshot的jar包
-
引入jar包即可
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.4-snapshot</version> </dependency>
1.3 关联源码
关联源码的目的是可以方便对源码进行编辑、注释。
1.3.1 Project Structure→Libraries,点击+
1.3.2 找到源码项目,添加
ok!!!
2 项目构建
2.1 db环境
2.1.1 本地环境准备
需要准备mysql和redis,本地搭建即可。此处省略,建议使用docker安装:https://docs.docker.com/docker-for-windows/install/
mysql | redis | |
---|---|---|
url | localhost:3306 | localhost:6379 |
数据库 | test_db | - |
账号 | root | - |
密码 | 123456 | - |
2.1.2 添加表
CREATE TABLE `t_department` (
`did` INT(10) NOT NULL AUTO_INCREMENT,
`d_name` VARCHAR(30) NOT NULL DEFAULT '' COLLATE 'utf8mb4_0900_ai_ci',
`d_desc` VARCHAR(50) NOT NULL DEFAULT '' COLLATE 'utf8mb4_0900_ai_ci',
PRIMARY KEY (`did`) USING BTREE
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
AUTO_INCREMENT=10002;
CREATE TABLE `t_user` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(20) NOT NULL DEFAULT '' COLLATE 'utf8mb4_0900_ai_ci',
`real_name` VARCHAR(30) NOT NULL DEFAULT '' COLLATE 'utf8mb4_0900_ai_ci',
`password` VARCHAR(50) NOT NULL DEFAULT '1234' COLLATE 'utf8mb4_0900_ai_ci',
`age` INT(10) NOT NULL DEFAULT '0',
`d_id` INT(10) NOT NULL DEFAULT '10000',
PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
AUTO_INCREMENT=5;
2.2 创建maven项目
new->project->maven->next
2.3 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4-snapshot</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
2.4 配置文件
位于resources目录
2.4.1 添加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>
<properties resource="db.properties"></properties>
<settings>
<!-- 打印查询语句 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 控制全局缓存(二级缓存),默认 true-->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false ,可通过select标签的 fetchType来覆盖 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false-->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
<!--<setting name="proxyFactory" value="CGLIB" />-->
<!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
<!--
<setting name="localCacheScope" value="STATEMENT"/>
-->
<setting name="localCacheScope" value="SESSION"/>
</settings>
<!-- 定义***Mapper.xml中resultMap的类型别名(别名的好处就是避免重复)-->
<typeAliases>
<typeAlias type="com.byron.tuhu.domain.User" alias="user"></typeAlias>
<typeAlias type="com.byron.tuhu.domain.Dept" alias="dept"></typeAlias>
</typeAliases>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名(要使用的话需添加pom依赖) -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样 -->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) -->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=start;pageSize=limit;"/>
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
2.4.2 添加db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test_db?characterEncoding=utf-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
jdbc.username=root
jdbc.password=123456
2.4.3 添加UserMapper.xml
- namespace对应的是mapper接口的全路径
- mapper中接口名以及参数和xml中sql标签一一对应
<?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.byron.tuhu.mapper.UserMapper">
<resultMap id="BaseResultMap" type="user">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="realName" column="real_name" jdbcType="VARCHAR" />
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="dId" column="d_id" jdbcType="INTEGER"/>
</resultMap>
<sql id="baseSQL">
id,user_name,real_name,password,age,d_id
</sql>
<select id="selectUserById" resultType="user" statementType="PREPARED" >
select
id,
user_name userName,
real_name realName,
password,
age,
d_id
from t_user where id = #{id}
</select>
<!-- $只能用在自定义类型和map上 -->
<select id="selectUserByBean" parameterType="user" resultMap="BaseResultMap" >
select * from t_user where user_name = '${userName}'
</select>
<select id="selectUserList" resultMap="BaseResultMap" >
select * from t_user
</select>
<select id="queryUserList" resultMap="BaseResultMap" >
select * from t_user
</select>
<insert id="insertUser" parameterType="user">
insert into t_user(user_name,real_name)values(#{userName},#{realName})
</insert>
<!-- 批量插入
insert into t_user() values (),(),(),()
-->
<insert id="insertUserList" parameterType="java.util.List" >
insert into t_user(user_name,real_name)
values
<foreach collection="list" item="user" separator=",">
(#{user.userName},#{user.realName})
</foreach>
</insert>
<update id="updateUserList">
update t_user set
user_name =
<foreach collection="list" item="user" index="index" separator=" " open="case id" close="end">
when #{user.id} then #{user.userName}
</foreach>
,real_name =
<foreach collection="list" item="user" index="index" separator=" " open="case id" close="end">
when #{user.id} then #{user.realName}
</foreach>
where id in
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.id,jdbcType=INTEGER}
</foreach>
</update>
<delete id="deleteByList" parameterType="java.util.List">
delete from t_user where id in
<!-- ( 1 , 2 , 3) -->
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.id,jdbcType=INTEGER}
</foreach>
</delete>
<!-- if 的使用 -->
<select id="selectListIf" parameterType="user" resultMap="BaseResultMap" >
select
<include refid="baseSQL"></include>
from t_user
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="userName != null">
and user_name = #{userName}
</if>
</where>
</select>
<!-- choose 的使用 -->
<select id="selectListChoose" parameterType="user" resultMap="BaseResultMap" >
select
<include refid="baseSQL"></include>
from t_user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="userName != null and userName != ''">
and user_name like CONCAT(CONCAT('%',#{userName,jdbcType=VARCHAR}),'%')
</when>
<otherwise>
</otherwise>
</choose>
</where>
</select>
<!--
trim 的使用
替代where标签的使用
-->
<select id="selectListTrim" resultMap="BaseResultMap"
parameterType="user">
select <include refid="baseSQL"></include>
<!-- <where>
<if test="username!=null">
and name = #{username}
</if>
</where> -->
<trim prefix="where" prefixOverrides="AND |OR ">
<if test="userName!=null">
and user_name = #{userName}
</if>
<if test="age != 0">
and age = #{age}
</if>
</trim>
</select>
<!-- 替代set标签的使用 -->
<update id="updateUser" parameterType="User">
update t_user
<trim prefix="set" suffixOverrides=",">
<if test="userName!=null">
user_name = #{userName},
</if>
<if test="age != 0">
age = #{age}
</if>
</trim>
where id=#{id}
</update>
<!-- 嵌套查询 1对1 1个用户对应一个部门-->
<resultMap id="nestedMap1" type="user">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="realName" column="real_name" jdbcType="VARCHAR" />
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="dId" column="d_id" jdbcType="INTEGER"/>
<association property="dept" javaType="dept">
<id column="did" property="dId"/>
<result column="d_name" property="dName"/>
<result column="d_desc" property="dDesc"/>
</association>
</resultMap>
<select id="queryUserNested" resultMap="nestedMap1">
SELECT
t1.`id`
,t1.`user_name`
,t1.`real_name`
,t1.`password`
,t1.`age`
,t2.`did`
,t2.`d_name`
,t2.`d_desc`
FROM t_user t1
LEFT JOIN
t_department t2
ON t1.`d_id` = t2.`did`
</select>
<!-- 延迟加载 1对1 -->
<resultMap id="nestedMap1Lazy" type="user">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="realName" column="real_name" jdbcType="VARCHAR" />
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="dId" column="d_id" jdbcType="INTEGER"/>
<association property="dept" javaType="dept" column="d_id" select="queryDeptByUserIdLazy">
</association>
</resultMap>
<resultMap id="baseDept" type="dept">
<id column="did" property="dId"/>
<result column="d_name" property="dName"/>
<result column="d_desc" property="dDesc"/>
</resultMap>
<select id="queryUserNestedLazy" resultMap="nestedMap1Lazy">
SELECT
t1.`id`
,t1.`user_name`
,t1.`real_name`
,t1.`password`
,t1.`age`
,t1.d_id
FROM t_user t1
</select>
<select id="queryDeptByUserIdLazy" parameterType="int" resultMap="baseDept">
select * from t_department where did = #{did}
</select>
<!-- 嵌套查询 1对多 1个部门有多个用户-->
<resultMap id="nestedMap2" type="dept">
<id column="did" property="dId"/>
<result column="d_name" property="dName"/>
<result column="d_desc" property="dDesc"/>
<collection property="users" ofType="user">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR" />
<result property="realName" column="real_name" jdbcType="VARCHAR" />
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="dId" column="d_id" jdbcType="INTEGER"/>
</collection>
</resultMap>
<select id="queryDeptNested" resultMap="nestedMap2">
SELECT
t1.`id`
,t1.`user_name`
,t1.`real_name`
,t1.`password`
,t1.`age`
,t2.`did`
,t2.`d_name`
,t2.`d_desc`
FROM t_user t1
RIGHT JOIN
t_department t2
ON t1.`d_id` = t2.`did`
</select>
<!-- 1对多 延迟加载 -->
<resultMap id="nestedMap2Lazy" type="dept">
<id column="did" property="dId"/>
<result column="d_name" property="dName"/>
<result column="d_desc" property="dDesc"/>
<collection property="users" ofType="user" column="did" select="queryUserByDeptLazy">
</collection>
</resultMap>
<select id="queryDeptNestedLazy" resultMap="nestedMap2">
SELECT
,t2.`did`
,t2.`d_name`
,t2.`d_desc`
FROM
t_department t2
</select>
<select id="queryUserByDeptLazy" resultMap="BaseResultMap" >
select * from t_user where d_id = #{did}
</select>
</mapper>
2.5 创建实体
2.5.1 添加domain
-
用户实体:
package com.byron.tuhu.domain; import lombok.Data; @Data public class User implements Serializable { private Integer id; private String userName; private String realName; private String password; private Integer age; private Integer dId; private Dept dept; }
-
部门实体:
package com.byron.tuhu.domain; import lombok.Data; import java.util.List; @Data public class Dept implements Serializable{ private Integer dId; private String dName; private String dDesc; private List<User> users; }
2.5.2 添加mapper接口
- 这个package和UserMapper.xml的namespace一一对应
- 接口名和id一致
package com.byron.tuhu.mapper;
import com.byron.tuhu.domain.Dept;
import com.byron.tuhu.domain.User;
import org.apache.ibatis.session.RowBounds;
import java.util.List;
public interface UserMapper {
public List<User> selectUserList();
public User selectUserById(Integer id);
public Integer insertUser(User user);
public Integer insertUserList(List<User> list);
public Integer updateUserList(List<User> list);
public Integer deleteByList(List<User> list);
public List<User> queryUserNested();
public List<User> queryUserNestedLazy();
public List<Dept> queryDeptNested();
public List<User> queryUserList(RowBounds rowBounds);
}
- 至此,项目基础搭建大功告成,项目结构如下:
3.CRUD操作
引入junit依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
3.1 查询
test->java添加ReadTest
3.1.1 普通查询
import com.byron.tuhu.domain.User;
import com.byron.tuhu.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.InputStream;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-07-29 08:13
* @ModifiedBy:byron
**/
public class ReadTest {
/**
* MyBatis API 的使用
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("com.byron.tuhu.mapper.UserMapper.selectUserList");
for (User user : list) {
System.out.println(user);
}
// 5.关闭会话
sqlSession.close();
}
/**
* MyBatis getMapper 方法的使用
*/
@Test
public void test2() throws Exception {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserList();
for (User user : list) {
System.out.println(user);
}
// 5.关闭会话
sqlSession.close();
}
}
3.1.2 关联查询
表之间关联查询,参考mapper配置文件的脚本,结合代码理解会好一点
import com.byron.tuhu.domain.Dept;
import com.byron.tuhu.domain.User;
import com.byron.tuhu.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;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-07-29 09:19
* @ModifiedBy:byron
**/
public class NestedTest {
public SqlSession session;
public void init() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
session = factory.openSession();
}
/**
* 1对1 关联查询
*
* @throws Exception
*/
@Test
public void test01() throws Exception {
init();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.queryUserNested();
for (User user : users) {
System.out.println(user + " " + user.getDept());
}
}
/**
* 1对1 关联查询 延迟加载
*
* @throws Exception
*/
@Test
public void test02() throws Exception {
init();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.queryUserNestedLazy();
for (User user : users) {
System.out.println(user.getUserName() + "---->" + user.getDept());
}
}
/**
* 1对多的关联关系查询
*
* @throws Exception
*/
@Test
public void test03() throws Exception {
init();
UserMapper mapper = session.getMapper(UserMapper.class);
List<Dept> depts = mapper.queryDeptNested();
for (Dept dept : depts) {
System.out.println(dept + " " + dept.getUsers());
}
}
}
3.1.3 分页查询
1. 逻辑分页
mybatis提供了Rowbounds对象,包含了offset和limit两个int属性,类似linq的skip然后take。
只需要在mapper接口中添加接口
public List<User> queryUserList(RowBounds rowBounds);
- 然后添加单元测试
@Test
public void test01() throws Exception{
init();
UserMapper mapper = session.getMapper(UserMapper.class);
RowBounds rowBounds = new RowBounds(1,3);
List<User> users = mapper.queryUserList(rowBounds);
for (User user : users) {
System.out.println(user);
}
}
- 运行观察打印sql
握草,为啥sql是物理分页呢???
- 原来是因为PageHelper插件
- 注释PageHelper后再观察
-
RowBounds逻辑分页原理
RowBounds的其实是对ResultSet的处理。它会舍弃掉前面offset条数据,然后再取剩下的数据的limit条。
// DefaultResultSetHandler.java private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext(); ResultSet resultSet = rsw.getResultSet(); this.skipRows(resultSet, rowBounds); while(this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, (String)null); Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null); this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } }
2.物理分页
取消注释PageHelper插件
还可以通过在查询之前添加代码
//获取第1页前5条
PageHelper.startPage(1,5);
List<User> users = mapper.selectUserList();
原理是通过提供Interceptor实现类,拦截query方法,然后改写sql,暂不展开。
3.2 新增
import com.byron.tuhu.domain.User;
import com.byron.tuhu.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;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-07-29 08:45
* @ModifiedBy:byron
**/
public class BathCreateTest {
public SqlSession session;
public void init() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
session = factory.openSession();
}
/**
* 循环插入10000
*/
@Test
public void test1() throws Exception {
init();
long start = System.currentTimeMillis();
UserMapper mapper = session.getMapper(UserMapper.class);
int count = 12000;
for (int i = 2000; i < count; i++) {
User user = new User();
user.setUserName("a" + i);
user.setRealName("A" + i);
user.setPassword("1234");
user.setAge(i / 30);
user.setDId(10000 + i);
mapper.insertUser(user);
}
session.commit();
session.close();
long end = System.currentTimeMillis();
System.out.println("循环批量插入" + count + "条,耗时:" + (end - start) + "毫秒");
}
/**
* 批量插入
*/
@Test
public void test2() throws Exception {
init();
long start = System.currentTimeMillis();
UserMapper mapper = session.getMapper(UserMapper.class);
int count = 12000;
List<User> list = new ArrayList<>();
for (int i = 2000; i < count; i++) {
User user = new User();
user.setUserName("a" + i);
user.setRealName("A" + i);
user.setPassword("1234");
user.setAge(i / 30);
user.setDId(10000 + i);
list.add(user);
}
mapper.insertUserList(list);
session.commit();
session.close();
long end = System.currentTimeMillis();
System.out.println("循环批量插入" + count + "条,耗时:" + (end - start) + "毫秒");
}
}
test1使用for循环逐条插入,执行结果:
test2使用批量插入,执行结果:
3.3 更新
import com.byron.tuhu.domain.User;
import com.byron.tuhu.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;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-07-29 08:56
* @ModifiedBy:byron
**/
public class BatchUpdateTest {
public SqlSession session;
public void init() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
session = factory.openSession();
}
/**
* 批量更新
*/
@Test
public void test() throws Exception {
init();
long start = System.currentTimeMillis();
UserMapper mapper = session.getMapper(UserMapper.class);
int count = 12000;
List<User> list = new ArrayList<>();
for (int i = 2000; i < count; i++) {
User user = new User();
user.setId(i);
user.setUserName("a" + i);
user.setRealName("A" + i);
user.setPassword("12345");
user.setAge(i / 40);
user.setDId(1000 + i);
list.add(user);
}
mapper.updateUserList(list);
session.commit();
session.close();
long end = System.currentTimeMillis();
System.out.println("批量更新" + count + "条,耗时:" + (end - start) + "毫秒");
}
}
3.4 删除
import com.byron.tuhu.domain.User;
import com.byron.tuhu.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;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-07-29 09:03
* @ModifiedBy:byron
**/
public class DeleteTest {
public SqlSession session;
public void init() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
session = factory.openSession();
}
/**
* 删除
*/
@Test
public void test() throws Exception {
init();
long start = System.currentTimeMillis();
UserMapper mapper = session.getMapper(UserMapper.class);
int count = 12000;
List<User> list = new ArrayList<>();
for (int i = 2000; i < count; i++) {
User user = new User();
user.setId(i);
list.add(user);
}
Integer realCount = mapper.deleteByList(list);
session.commit();
session.close();
long end = System.currentTimeMillis();
System.out.println("批量删除" + realCount + "条,耗时:" + (end - start) + "毫秒");
}
}
4.延迟加载
延迟加载:就是在你要用到的时候才去数据库查询,也叫按需查询或懒加载。
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
<setting name="aggressiveLazyLoading" value="false"/>
代码位于3.1.1 关联查询
-
设置lazyLoadingEnable为false,运行test02,打断点观察,搜索Preparing,可以看到部门表也被查询,比较常规
-
设置lazyLoadingEnable为true,打断点观察
执行use.getDept之前:只查询了用户表
执行后:延迟查询部门表
五、Mybatis进阶
1.缓存
1.1 一级缓存
一级缓存也叫本地缓存(Local Cache),MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis的一级缓存是默认开启的,不需要任何的配置。
在BaseExecutor对象的query方法中有关闭一级缓存的逻辑(如下,如果要关闭,localCacheScope设置为STATEMENT即可)。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
//配置<setting name="localCacheScope" value="STATEMENT"/>
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
//清除缓存
this.clearLocalCache();
}
}
return list;
}
}
- 测试一级缓存,添加CachedTest测试类,首先用session1做两次同样的查询:
import com.byron.tuhu.domain.User;
import com.byron.tuhu.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;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-08-02 08:40
* @ModifiedBy:byron
**/
public class CachedTest {
@Test
public void test01() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user.getUserName());
User user1 = userMapper.selectUserById(1);
System.out.println(user1.getUserName());
}
}
- 观察打印的sql,发现只访问了一次数据库
其它测试场景:session1同方法不同参数、session1和session2同方法同参数、关闭一级缓存session1同方法同参数。(略)
1.2 二级缓存
1.2.1 生命周期
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。
- 二级缓存的设置,首先是settings中的cacheEnabled要设置为true,当然默认的就是为true,这个步骤决定了在创建Executor对象的时候是否通过CachingExecutor来装饰。
- 但是,这样并不能让二级缓存生效,还需要在mapper配置中添加cache标签才行:
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024"
eviction="LRU"
flushInterval="120000"
readOnly="false"/>
属性 | 说明 | 取值 |
---|---|---|
type | 缓存实现类 | 需要实现Cache接口,默认PerpectualCache,可以使用三方缓存 |
size | 最大缓存个数 | 默认1024 |
eviction | 缓存淘汰算法 | LRU – 最近最少使用的:移除最长时间不被使用的对象(默认)。 FIFO– 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象 |
flushInterval | 执行缓存清除的间隔 | 单位ms,即缓存失效的绝对时间,不配置则调用insert/update时缓存失效 |
readOnly | 是否只读 | true:只读缓存,会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。 false:读写缓存;会返回缓存对象的拷贝(通过序列化),不会共享。这会慢一些,但是安全,因此默认是 false。改为false可读写时,对象必须支持序列化。 |
blocking | 启用阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现 |
-
再看源码中的实现,根据sql创建CacheKey
-
二级缓存对于当前映射文件中的所有查询都生效,如果针对某个查询想关闭二级缓存,只需要添加属性useCache=“false”
-
当我们执行DML操作时会清空一、二级缓存
1.2.2 代码演示
- 创建2个会话,分别执行同一个查询
@Test
public void test01() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1);
System.out.println(user1);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectUserById(1);
System.out.println(user2);
}
-
我ca,没命中,翻车!!!
-
经过不断地探索(百度),原来如此,session1需要先提交,然后session2才能命中啊
close()中会执行提交操作
至此,浅显的二级缓存探索结束,跨不同的namespace以及cache-ref的使用也就不展开了。
1.3 三方缓存
实际生产中,都是分布式系统,很少会采用本地二级缓存,mybatis提供三方缓存工具来实现,比如redis。
https:github.com/mybatis/redis-cache
-
添加依赖
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency>
-
更改二级缓存配置文件
<cache type="org.mybatis.caches.redis.RedisCache" size="1024" eviction="LRU" flushInterval="120000" readOnly="false"/>
-
添加redis.properties配置
host=localhost port=6379 connectionTimeout=5000 soTimeout=5000 database=0
-
运行CachedTest.cs
-
查看redis,数据已经同步到redis,留意一下这个hasmap的name,哈,就是namespace,hash的key是sql语句
至于redis为什么能替换PERPETUAL,具体实现原理同样不展开,可以参见源码XmlMapperBuilder.java的cacheElement方法。
2.TypeHandler
由于Java类型和数据库的JDBC类型不是一一对应的(比如String与varchar、char、text),所以我们把Java对象转换为数据库的值,和把数据库的值转换成Java对象,需要经过一定的转换,这两个方向的转换就要用到TypeHandler。
当参数类型和返回值是一个对象的时候,我没有做任何的配置,为什么对象里面的一个String属性,可以转换成数据库里面的varchar字段?
这是因为MyBatis已经内置了很多TypeHandler(在type包下),它们全部全部注册在TypeHandlerRegistry中,他们都继承了抽象类BaseTypeHandler,泛型就是要处理的Java数据类型。
这个也是为什么大部分类型都不需要处理。当我们查询数据和登记数据,做数据类型转换的时候,就会自动调用对应的TypeHandler的方法。
为了加深理解,我们来实现一个自定义的TypeHandler:查询User表,如果数据中存在值为zhaosi的人,返回数据时前缀加上:【东北舞王】
-
首先在typeHandler下添加ZhaoSiHandler.java
/** * @Author: byron * @Description: * @Date: 2021-08-03 23:38 * @ModifiedBy:byron **/ package com.byron.tuhu.typeHandler; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class ZhaoSiHandler extends BaseTypeHandler<String> { /** * 插入数据时回调 * * @param ps * @param i * @param parameter * @param jdbcType * @throws SQLException */ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { } /** * 获取数据时回调 * * @param rs * @param columnName * @return * @throws SQLException */ @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String val = rs.getString(columnName); if ("zhaosi".equalsIgnoreCase(val)) { return "【东北舞王】" + val; } return val; } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String val = rs.getString(columnIndex); if ("zhaosi".equalsIgnoreCase(val)) { return "【东北舞王】" + val; } return val; } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String val = cs.getString(columnIndex); if ("zhaosi".equalsIgnoreCase(val)) { return "【东北舞王】" + val; } return val; } }
-
然后mybatis-config中注册
<typeHandlers> <typeHandler handler="com.byron.tuhu.typeHandler.ZhaoSiHandler"></typeHandler> </typeHandlers>
-
mapper中对应的列配置生效
-
执行ReadTest,切记先关闭二级缓存,因为本人被那玩意坑了半天
东北舞王登场了!!!
3.MBG
官方文档:http://mybatis.org/generator/
我们在项目中使用MyBaits的时候,针对需要操作的一张表,需要创建实体类、Mapper映射器、Mapper接口,里面又有很多的字段和方法的配置,这部分的工作贼jr恶心。而大部分时候我们对于表的基本操作是相同的,比如根据主键查询、根据Map查询、单条插入、批量插入、根据主键删除等等等等。当我们的表很多的时候,意味着有大量的重复工作,违背DRY。
所以有没有一种办法,可以根据我们的表,自动生成实体类、Mapper映射器、Mapper接口,里面包含了我们需要用到的这些基本方法和SQL呢?
MyBatis也提供了一个代码生成器,叫做MyBatis Generator,简称MBG(它是MyBatis的一个插件)。我们只需要修改一个配置文件,使用相关的jar包命令或者Java代码就可以帮助我们生成实体类、映射器和接口文件。这也是我们目前工作中在使用的一种模式。
MBG的配置文件里面有一个Example的开关,这个东西用来构造复杂的筛选条件的,换句话说就是根据我们的代码去生成where条件。
原理:在实体类中包含了两个有继承关系的Criteria(这玩意我们熟悉吧!),用其中自动生成的方法来构建查询条件。把这个包含了Criteria的实体类作为参数传到查询参数中,在解析Mapper映射器的时候会转换成SQL条件。
3.1 添加插件
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<!-- 指定配置文件的位置 -->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
3.2 添加配置文件
文件名:generatorConfig.xml
location位置使用全路径,指向mysql-connector-java-X*.X*.X.jar
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--mysql 连接数据库jar 这里选择自己本地仓库位置-->
<classPathEntry location="E:\maven\repository\mysql\mysql-connector-java\8.0.11\mysql-connector-java-8.0.11.jar" />
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false" userId="root"
password="123456">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="com.byron.tuhu.domain"
targetProject="src/main/java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置
如果maven工程只是单独的一个工程,targetProject="src/main/java"
若果maven工程是分模块的工程,targetProject="所属模块的名称",例如:
targetProject="ecps-manager-mapper",下同-->
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.byron.tuhu.mapper"
targetProject="src/main/java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="t_user" domainObjectName="UserMGB" enableCountByExample="true" enableUpdateByExample="true"
enableDeleteByExample="true" enableSelectByExample="true"
selectByExampleQueryId="true">
<columnOverride column="d_id" property="departmentId" javaType="java.lang.Integer"/>
</table>
</context>
</generatorConfiguration>
3.3 生成代码
运行插件,成功后即可看到项目路径下自动生成了项目文件
3.4 测试demo
3.4.1 配置config
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/UserMGBMapper.xml"/>
</mappers>
3.4.2 MGBTest.java
import com.byron.tuhu.domain.User;
import com.byron.tuhu.domain.UserMGB;
import com.byron.tuhu.domain.UserMGBExample;
import com.byron.tuhu.mapper.UserMGBMapper;
import com.byron.tuhu.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;
import java.util.List;
/**
* @Author: byron
* @Description:
* @Date: 2021-08-05 09:37
* @ModifiedBy:byron
**/
public class MGBTest {
@Test
public void test01() throws IOException {
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMGBMapper mapper = sqlSession.getMapper(UserMGBMapper.class);
UserMGBExample example = new UserMGBExample();
UserMGBExample.Criteria criteria = example.createCriteria();
criteria.andIdEqualTo(2);
List<UserMGB> userMGBS = mapper.selectByExample(example);
for (UserMGB user : userMGBS) {
System.out.println(user);
}
// 5.关闭会话
sqlSession.close();
}
}
运行,成功:
4.MyBatis-Plus
https://mp.baomidou.com/
MyBatis-Plus的核心功能:
**通用 CRUD:**定义好Mapper接口后,只需要继承BaseMapper 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件。
**条件构造器:**通过EntityWrapper(实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的SQL。
**代码生成器:**支持一系列的策略配置与全局配置,比MyBatis的代码生成更好用。
**分页:**内置分页插件,基于mybatis实现物理分页,写分页就像普通list查询一样easy。
进一步深入依然不做展开,动手即可,一个字:干!
六、FQA
-
报错:Cause: java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
官方解释:https://mysqlconnector.net/connection-options/
解决办法:url后追加allowPublicKeyRetrieval=true
-
批量操作限制
- MyBatis的动态标签的批量操作存在一定的缺点,比如数据量特别大的时候,拼接出来的SQL语句过大。
- MySQL的服务端对于接收的数据包有大小限制,max_allowed_packet 默认是 4M,需要修改默认配置或者手动地控制条数
-
延迟aggressiveLazyLoading配置
如果设置为true,那其实lazyLoadingEnabled=true毫无意义
-
为什么二级缓存开启还要添加cache标签?
参见源码:
-
MGB生成代码时报错:The specified target project directory .src/main/java does not exist
Edit Configrations…–>working derictory,复制出来使用全路径,比如