言成言成啊 | Kit Chen's Blog

Mybatis的CRUD

发布于2021-01-25 00:41:20,更新于2021-04-10 15:00:23,标签:sql mybatis  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

目录

  1. Mybatis的CRUD
  2. Mybatis的缓存及注解开发
  3. 源码

学一样东西,当然是先看官网文档啦!

什么是框架?

框架是软件开发中一套解决方案,不同的框架解决的是不同的问题。

使用框架的好处?

框架封装了很多细节,使开发者可以使用极简的方式实现功能,大大提高开发效率。

什么是三层架构?

  • 表现层:用于展示数据的
  • 业务层:处理业务需求的
  • 持久层:和数据库交互的

像MVC属于表现层框架,而Mybatis属于持久层框架

至于MVC与三层架构的关系,可以参照下图

持久层技术解决方案?

  • JDBC技术
    • Connection
    • PreparedStatement
    • ResultSet
  • Spring的JdbcTemplate:Spring中对jdbc的简单封装
  • Apache的DBUtils:它和Spring的jdbcTemplate类似,对jdbc简单封装
  • Mybatis

jdbc是规范,JdbcTemplate和DBUtils是工具类。

而Mybatis就属于持久层框架。

以下提到的pojo对象,本质就是javabean对象,即包含set与get的类

一、Mybatis入门

1.1 概述

Mybatis是一个持久层框架,用java编写,它封装了jdbc操作的许多细节,使开发者只需要关注SQL本身,而无需关注注册驱动、创建连接等繁杂过程。

采用ORM(Object Relational Mapping)思想解决了实体和数据库映射的问题,对JDBC进行封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。

ORM:Object Relational Mapping 对象关系映射

  • 将数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就能实现操作数据库表。

Mybatis通过xml或者注解的方式将要执行的各种Statement配置起来,并通过Java对象和Statement中SQL的动态参数进行映射生成最终执行的SQL语句,最后由Mybatis框架执行SQL并将结果映射为Java对象并返回。

1.2 环境搭建

步骤

  1. 创建Maven工程导入依赖坐标

  2. 创建实体类Person和实体的接口PersonDao

  3. 创建Mybatis的主配置文件:SQLMapConfig.xml

  4. 配置log4j:在resources下导入log4j的配置文件,配置文件的使用参照

创建Maven工程导入依赖坐标

去mybatis官网,找到入门,参照maven配置,本地创建项目、修改打包方式为jar包、添加依赖。我的pom.xml如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.meethigher</groupId>
<artifactId>Mybatis-notes</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

创建Mybatis的主配置文件

至于Mybatis的主配置文件,参照官网入门的教程

下面放上我的配置

SqlMapConfig.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/beauty?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<!-- xml方式 通过resource加载配置文件 -->
<mapper resource="top/meethigher/demo01/dao/PersonDao.xml"/>
<!-- 注解方式 通过class加载类文件 -->
<mapper class="top.meethigher.demo02.dao.PersonDao"/>
</mappers>
</configuration>

配置log4j

log4j.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#############
# 输出到控制台
#############

# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
# WARN:日志级别 CONSOLE:输出位置自己定义的一个名字 logfile:输出位置自己定义的一个名字
log4j.rootLogger=debug,CONSOLE,logfile
# 配置CONSOLE输出到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 配置CONSOLE设置为自定义布局模式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 配置CONSOLE日志的输出格式 [frame] 2019-08-22 22:52:12,000 %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n

################
# 输出到日志文件中
################

# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
# 保存编码格式
log4j.appender.logfile.Encoding=UTF-8
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.logfile.File=logs/root.log
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.logfile.MaxFileSize=10MB
# 设置滚定文件的最大值3 指可以产生root.log.1、root.log.2、root.log.3和root.log四个日志文件
log4j.appender.logfile.MaxBackupIndex=3
# 配置logfile为自定义布局模式
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

##########################
# 对不同的类输出不同的日志文件
##########################

# club.bagedate包下的日志单独输出
log4j.logger.club.bagedate=DEBUG,bagedate
# 设置为false该日志信息就不会加入到rootLogger中了
log4j.additivity.club.bagedate=false
# 下面就和上面配置一样了
log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
log4j.appender.bagedate.Encoding=UTF-8
log4j.appender.bagedate.File=logs/bagedate.log
log4j.appender.bagedate.MaxFileSize=10MB
log4j.appender.bagedate.MaxBackupIndex=3
log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

至于日志的详细配置,参考文章

1.3 入门案例

在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式,不管是xml配置还是注解配置。

但是Mybatis是支持写dao实现类的。下面的案例是通过mybatis提供的代理dao实现的。

步骤

  1. 读取配置文件
  2. 创建SqlSessionFactory工厂
  3. 创建SqlSession
  4. 创建Dao接口的代理对象(动态代理)
  5. 执行Dao接口中的方法
  6. 释放资源

MybatisTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MybatisTest {
/**
* 入门案例
*
* @param args
*/
public static void main(String[] args) throws Exception {
//1.读取配置文件,java中获取路径方式总结https://meethigher.top/blog/2021/java-path/
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.动态代理,使用SqlSession创建Dao接口的代理对象
//理解动态搭理:https://meethigher.top/blog/2020/filter-and-listener/#%E7%90%86%E8%A7%A3%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86demo
PersonDao personDao = session.getMapper(PersonDao.class);
//5.使用代理对象执行方法
List<Person> persons =personDao.findAll();
for(Person p:persons){
System.out.println(p);
}
//6.释放资源
session.close();
is.close();
}
}

整个的思路就是,Mybatis将sql语句查询出的结果集,通过动态代理的方式,创建出代理对象,代理对象内部增强了原接口的方法(不用实现类),将查询到的数据,根据xml中配置,封装到指定类中,然后再添加到list中,返回数据。

XML方式

在resource下,创建映射配置文件,top/meethigher/demo01/dao/PersonDao.xml。

namespace指向dao接口的全限定类名,select表示查询,id表示接口中的方法名,resultType是指要将获取到的数据封装成的类型,要写全限定类名。

1
2
3
4
5
6
7
8
9
10
<?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="top.meethigher.demo01.dao.PersonDao">
<!--配置查询所有 id为方法名称,将获取到的数据封装到resultType这里面,然后mybatis可以通过反射将结果集封装成resultType类型-->
<select id="findAll" resultType="top.meethigher.demo01.domain.Person">
select * from fairy
</select>
</mapper>

注意

切记不能忘记在映射配置中告知mybatis要封装到哪个实体类中

同时要在主配置文件SqlMapConfig.xml下,配置xml加载配置文件,配置为指定实体类的全限定类名

1
2
3
4
<mappers>
<!-- xml方式 通过resource加载配置文件 -->
<mapper resource="top/meethigher/demo01/dao/PersonDao.xml"/>
</mappers>

整个结构

环境搭建注意事项

  1. 创建PersonDao.java和PersonDao.xml,在Mybatis中把持久层的操作接口名称的映射文件也叫做Mapper
  2. idea创建directory和package不一样。直接通过xx.xx.xx创建,如果directory,则创建出的是一级目录,如果是package,则创建出的是三级目录
  3. mybatis的映射配置必须和dao接口的包结构相同
  4. 映射配置文件mapper的namespace标签属性的值必须是dao接口的全类名
  5. 映射配置文件的操作配置为select,id属性取值必须是dao接口的方法名

注解方式

注解方式相对来说比较简便,不再需要额外添加一个配置文件,只需要在PersonDao接口添加注解即可。

1
2
3
4
5
6
7
8
public interface PersonDao {
/**
* 查询所有操作
* @return
*/
@Select("select * from fairy")
List<Person> findAll();
}

同时在主配置文件SqlMapConfig.xml下,配置注解加载类文件的方式。

1
2
3
4
<mappers>
<!-- 注解方式 通过class加载类文件 -->
<mapper class="top.meethigher.demo02.dao.PersonDao"/>
</mappers>

1.4 设计模式分析

入门Mybatis中的几大步骤使用的设计模式

  • 构建者模式
  • 工厂模式
  • 代理模式

在Mybatis中,创建工厂SqlSessionFactoryBuilder,使用了构建者模式。

构建者模式:把对象的创建细节隐藏,使使用者直接调用方法即可拿到对象。

生产SqlSession,使用了工厂模式,优势是解耦(降低类之间的依赖关系)。

创建Dao接口实现类,使用了代理模式,优势是在不修改源码的基础上进行增强。

由上图,总结了下java获取文件路径

1.5 自定义实现类

在Mybatis中,可以自己写dao的实现类,但是没必要,因为Mybatis已经通过动态代理的方式实现了,开发者只需要关注sql本身即可

下面以用xml配置方式来自定义实现类为例。

创建demo03,在原先的基础上,创建PersonDaoImpl.java,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PersonDaoImpl implements PersonDao {
private SqlSessionFactory factory;
public PersonDaoImpl(SqlSessionFactory factory){
this.factory=factory;
}
@Override
public List<Person> findAll() {
//1.使用工厂创建SqlSession对象
SqlSession session = factory.openSession();
//2.使用session执行查询所有方法,里面的参数是配置文件里指定命名空间下的某个语句id
List<Person> persons = session.selectList("top.meethigher.demo03.dao.PersonDao.findAll");
session.close();//关闭
//3.返回查询结果
return persons;
}
}

在SqlMapConfig.xml下,配置指定的mapper

1
2
3
<mappers>
<mapper resource="top/meethigher/demo03/dao/PersonDao.xml"/>
</mappers>

测试类里面,可以省略掉动态代理获取增强后的代理对象的步骤,可以直接获取dao实现类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MybatisTest03 {
/**
* 入门案例
* 使用自定义的PersonDao实现类
*
* @param args
*/
public static void main(String[] args) throws Exception {
//1.读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3.使用工厂创建Dao对象
PersonDaoImpl personDao = new PersonDaoImpl(factory);

//4.使用代理对象执行方法
List<Person> persons =personDao.findAll();
for(Person p:persons){
System.out.println(p);
}
//5.释放资源
is.close();
}
}

1.6 自定义Mybatis

思路分析

通过上面的自定义实现类,我们不难发现mybatis使用代理dao对象的方式做的两件事

  1. 创建代理对象,Dao的代理实现类
  2. 调用session的selectList方法,封装成指定类型

以xml配置方式为例,对于配置文件的读取,用到的技术就是xml解析,自定义mybatis使用的是dom4j。

首先在主配置文件,可以获取到数据库的信息,有了他们就能创建Connection对象,还可以获取到映射配置文件的路径。

通过映射配置文件就能找到执行的sql语句,以及要封装到实体的全限定类名,映射配置文件中的sql语句跟封装实体的全限定类名,可以封装成一个javabean对象Mapper,两个属性querySql跟resultType。

键为命令空间.id,值为Mapper对象,里面有sql语句跟domain的全限定类名。

比如Map<String,Mapper>,Mapper里面有两个属性,两个属性querySql跟resultType。

  • 键:top.meethigher.demo03.dao.PersonDao.findAll
  • 值:Mapper对象

如此便可以获取到PreparedStatement,有了全限定类名,我们可以通过反射来封装数据集。

1
2
// 通过反射实例化类对象
Person person=(Person)Class.forName("top.meethigher.demo03.dao.PersonDao").newInstance();

mybatis中通过SqlSession创建代理对象

1
PersonDao personDao = session.getMapper(PersonDao.class);

getMapper实现的原理如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public <T> getMapper(Class <T> daoInterfaceClass){
/**
* 类加载器:和被代理对象相同的类加载器
* 代理对象要实现的接口数组:和被代理对象相同的接口,因为传过来的本身就是个接口,所以直接new一个数组即可,若为实体类,可以通过xxx.getInterfaces()
* 如何代理:通过匿名内部类的方式实现InvocationHandler接口,进行增强,在内部调用selectList;当然了,不使用匿名内部类,新建一个实现类也ok啦
*/
Proxy.newProxyInstance(类加载器,代理对象要实现的接口数组,如何代理);
Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},
new InvocationHandler(){
...
})
}

实现

在pom中删除mybatis的依赖,根据下面mybatis类和接口创建自定义的

  • org.apache.ibatis.io.Resources;
  • org.apache.ibatis.session.SqlSession;
  • org.apache.ibatis.session.SqlSessionFactory;
  • org.apache.ibatis.session.SqlSessionFactoryBuilder;

在不修改MybatisTest源码的基础上,通过自定义的Mybatis实现类似的功能。

以上都好理解,我比较疑惑的是,为啥setMappers中要用putAll?那么mybatis又是如何区分要用哪一条Mapper的呢?

思考了半天,搞明白了。在XMLConfigBuilder解析配置文件时,将所有的映射都存入到mapper中,从class或者resource中判断是否为注解配置或者xml配置。

若为xml配置,则获取到映射配置文件中的命名空间.id(方法的全名),作为键;sql语句以及resultType作为Mapper对象的属性,Mapper对象为值。由此,一个方法的全名对应一个Mapper对象。

若为注解配置,则取出注解中的value值,作为sql语句,获取当前方法的返回值作为resultType,由此构造出一个Mapper对象作为值,获取方法的全名,作为键。由此,又是一一对应。

在MapperProxy中,通过代理模式,判断调用的方法名,以及该方法所在的全类名,将全类名.方法名拼接起来,就是键,从而可以获取到需要调用哪一条mapper。

在注解配置中,需要获取参数化类型。

比如ArrayList<E>就是泛型类型,其中E是泛型,相当于类型的占位符,传入Integer变成ArrayList<Integer>,就成为参数化类型ParameterizedType,通过参数化类型,可以获得其泛型类型。下面放置部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws Exception {
//定义返回值对象
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
//1.得到dao接口的字节码对象
Class daoClass = Class.forName(daoClassPath);
//2.得到dao接口中的方法数组
Method[] methods = daoClass.getMethods();
//3.遍历Method数组
for (Method method : methods) {
//取出每一个方法,判断是否有select注解
boolean isAnnotated = method.isAnnotationPresent(Select.class);
if (isAnnotated) {
//创建Mapper对象
Mapper mapper = new Mapper();
//取出注解的value属性值
Select selectAnno = method.getAnnotation(Select.class);
String queryString = selectAnno.value();
mapper.setQueryString(queryString);
//获取当前方法的返回值,还要求必须带有泛型信息
Type type = method.getGenericReturnType();//List<User>
//判断type是不是参数化的类型
if (type instanceof ParameterizedType) {
//强转
ParameterizedType ptype = (ParameterizedType) type;
//得到参数化类型中的实际类型参数
Type[] types = ptype.getActualTypeArguments();
//取出第一个
Class domainClass = (Class) types[0];
//获取domainClass的类名
String resultType = domainClass.getName();
//给Mapper赋值
mapper.setResultType(resultType);
}
//组装key的信息
//获取方法的名称
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String key = className + "." + methodName;
//给map赋值
mappers.put(key, mapper);
}
}
return mappers;
}

综上,整个具体的流程图如下

二、Mybatis使用

2.1 单表CRUD操作

以demo5为例

添加☆的是不常规的,需要特殊了解。

在Mybatis中,事务是默认关闭自动提交的,在提交时,需要手动添加代码提交,否则尽管在代码执行时能获取或者添加数据,但实际数据库又会回滚了。

首先,测试预置代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MybatisTest05 {
private InputStream is;
private SqlSession sqlSession;
private PersonDao personDao;

@Before //用于在测试方法之前执行
public void init() {
//1.读取配置文件,获取输入流
try {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
//2.获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//3.获取SqlSession对象
sqlSession = factory.openSession();
//4.获取代理dao对象
personDao = sqlSession.getMapper(PersonDao.class);
}

@After //用于在测试方法之后执行
public void destroy() throws IOException {
//提交事务
sqlSession.commit();
if (sqlSession != null)
sqlSession.close();
if (is != null)
is.close();
}

//下面是一系列测试方法
}

☆增加Create

XML配置

dao接口中添加

1
2
3
4
5
/**
* 保存数据
* @param person
*/
void savePerson(Person person);

dao配置文件mapper中添加

1
2
3
4
5
6
7
8
<!--保存用户,parameterType是变量的类型,#{变量类型里的对应属性名}-->
<insert id="savePerson" parameterType="top.meethigher.demo05.domain.Person">
<!--在插入After之后,获取数据库中的id列,作为Integer类型的id属性(与实体类型中的id对应)返回-->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into fairy(name,gender,age,grade,school,position) values(#{name},#{gender},#{age},#{grade},#{school},#{position});
</insert>

如果没有selectKey,则只会添加数据。

如果我们还要获取插入之后返回的id,那么我们就可以调用sql内置函数

1
2
前置sql;
select last_insert_id();

测试代码如下

1
2
3
4
5
6
7
8
9
10
@Test
public void testSave() throws IOException {
Person person = new Person();
...通过set方法设置属性...
System.out.println("插入前:"+person);
personDao.savePerson(person);
//提交事务
sqlSession.commit();//为了方便使用,可将该代码,放到destroy方法中,设置destroy为@After,表示执行测试后再执行
System.out.println("插入后:"+person);//该行代码不管放到commit前或是commit后,都会有值,原因是代码执行插入,已经获取到结果了,但是由于没提交,后来数据库又回滚了。
}

实现过程是将查询到上次插入的数据库中的列(keyColumn)中的id值,存入到Person中的id属性(keyProperty),执行顺序是执行insert中语句之后。

修改Update

XML配置

dao接口中添加

1
2
3
4
5
/**
* 更新数据
* @param person
*/
void updatePerson(Person person);

dao配置文件中添加

1
2
3
4
<!--更新用户-->
<update id="updatePerson" parameterType="top.meethigher.demo05.domain.Person">
update fairy set name=#{name},gender=#{gender},age=#{age},grade=#{grade},school=#{school},position=#{position} where id=#{id};
</update>

测试代码

1
2
3
4
5
6
@Test
public void testUpdatePerson() {
Person person = new Person();
... set设置属性 ...
personDao.updatePerson(person);
}

删除Delete

XML配置

dao接口中添加

1
2
3
4
5
/**
* 删除数据
* @param id
*/
void deletePerson(Integer id);

dao配置文件中添加

1
2
3
4
<!--删除用户 变量类型可以为Integer、Int、java.lang.Integer,如果变量类型是一个参数,我们只需要写清楚一个占位符即可,名字可以任意-->
<delete id="deletePerson" parameterType="Integer">
delete from fairy where id=#{uid};
</delete>

测试代码

1
2
3
4
@Test
public void testDeletePerson() {
personDao.deletePerson(13);
}

☆查询Retrieve

XML配置

dao接口添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 查询所有操作
* @return
*/
List<Person> findAll();
/**
* 通过id查询
* @param id
* @return
*/
Person findPersonById(Integer id);
/**
* 模糊查询
* @param name
* @return
*/
List<Person> findByName(String name);
/**
* 查询总用户数
* @return
*/
int findTotal();

dao配置文件mapper中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--配置查询所有 id为方法名称,将获取到的数据封装到resultType这里面-->
<select id="findAll" resultType="top.meethigher.demo05.domain.Person">
select * from fairy;
</select>
<!--根据id查询-->
<select id="findPersonById" parameterType="Integer" resultType="top.meethigher.demo05.domain.Person">
select * from fairy where id=#{id};
</select>
<!--根据名称模糊查询-->
<select id="findByName" parameterType="String" resultType="top.meethigher.demo05.domain.Person">
<!-- 这种写法需要用户在调用方法时,自己传入模糊的语法 -->
<!-- select * from fairy where name like #{name}; -->
<!-- 这种的只需要调用方法时,传入关键字即可 -->
select * from fairy where name like '%${value}%';
</select>
<!--查询总用户数-->
<select id="findTotal" resultType="Integer">
select count(id) from fairy;
</select>

测试代码就不放置了,也很简单,看懂上面增删改的,这个也就会写了

此处重点了解的是模糊查询中mybatis的语法

法一:

1
select * from fairy where name like #{name};

这种方法需要在传入Person的name时,携带模糊查询中的%号

1
List<Person> persons = personDao.findByName("%月%");

打开日志debug模式,运行junit测试,会发现以下debug

变量是String类型,可知该方法时通过PreparedStatement通过占位符来预编译sql实现查询的,相对法二,法一更安全,推荐使用

法二:

1
select * from fairy where name like '%${value}%';

这种方法只需要在代码中传入关键字name即可,不用携带%

1
List<Person> persons = personDao.findByName("月");

打开日志debug模式,运行junit测试,会发现以下debug

变量是空,可知该方法是通过Statement静态sql(直接拼接)实现查询的,会造成安全性问题,比如sql注入。

#{}与${}区别及源码分析

#{}表示一个占位符号

通过#{}可以实现PreparedStatement向占位符中设置值,自动进行Java类型和JDBC类型转换,#{}可以有效防止SQL注入。#{}可以接受简单类型值或者JavaBean或者POJO属性值。如果ParameterType是个简单类型值,比如int(Integer)、String,#{}括号中,可以是任意名称。

${}表示拼接sql串

通过${}可以将ParameterType传入的内容拼接在SQL中且不进行JDBC类型转换,${}可以接收简单类型值,比如Integer、String。如果ParameterType是个简单类型值,${}括号中名称只能是value。

org.apache.ibatis.scripting.xmltags.TextSqlNode源码中已经写死了。

2.2 参数和返回值

参数

参数可以是简单类型,也可以是pojo对象(即JavaBean),还可以是包装对象。

  • 简单类型:如,Integer、String等,#{}或者${}内部可以任意名称;

  • pojo对象,#{}或者${}只能为对应的属性名称;在mybatis中,使用ognl表达式来解析对象字段的值。

  • pojo包装对象,一个pojo中封装多个pojo,用于综合查询。

ognl表达式

ognl表达式:Apache开发,Object Graphic Navigation Language,即对象图导航语言。

它是通过对象的取值方法,来获取数据,在写法上,把get省略

比如,获取id

  • 类中写法,获取Person对象,再获取id属性:dao.getPerson().getId();
  • ognl写法,可以连续点:dao.person.id;

在xml配置中,#{}或者${}不需要再加类名,原因是parameterType中已经设置了全类名,mybatis会自动通过.拼接。

比如

1
2
3
<update id="updatePerson" parameterType="top.meethigher.demo05.domain.Person">
update fairy set name=#{name},gender=#{gender},age=#{age},grade=#{grade},school=#{school},position=#{position} where id=#{id};
</update>

综合查询

比如,查询某个单位某个名称的人,那么只需要一个pojo(javabean)对象是不行的,可以通过pojo类中包含pojo

那我们可以创建一个包装类,比如起名QueryVo,该pojo里面封装有Unit和Person的两个不同类型的pojo。

QueryVo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//包含综合条件的包装类
//该接口是一个mini接口,没有必须要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,不加也一样用
public class QueryVo implements Serializable {
private Person person;
private Unit unit;

public Person getPerson() {
return person;
}

public void setPerson(Person person) {
this.person = person;
}

public Unit getUnit() {
return unit;
}

public void setUnit(Unit unit) {
this.unit = unit;
}
}

PersonDao.xml

1
2
3
4
<!--根据QueryVo中条件查询-->
<select id="findPersonByVo" parameterType="top.meethigher.demo05.domain.QueryVo" resultType="top.meethigher.demo05.domain.Person">
select * from fairy where name like #{person.name} and u_id=#{unit.u_id};
</select>

PersonDao.java添加方法

1
List<Person> findPersonByVo(QueryVo vo);

测试类中这么写

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testFindPersonByVo(){
QueryVo queryVo = new QueryVo();
Person person = new Person();
person.setName("%月%");
Unit unit = new Unit();
unit.setId(1);
queryVo.setPerson(person);
queryVo.setUnit(unit);
List<Person> personByVo = personDao.findPersonByVo(queryVo);
System.out.println(personByVo);
}

这个地方的我写的可能有点问题,如果实在看不明白,就直接跳到多表查询

返回值

返回值有两种

  • 简单类型:Integer、String、Boolean等

  • pojo对象

  • pojo列表

返回值Null问题

在使用过程中,如果pojo对象如果mysql中的属性不对应,会导致返回的数据无法存储。

比如

实体主键属性普通属性
数据库idusername
pojo类userIduserName

执行插入更改删除之类的,是没问题的,但如果是执行返回数据,像userId就会封装失败。但是username在windows上是封装成功的,在linux是封装失败的。

原因是mysql在windows不区分大小写,在linux严格遵守大小写。

如果修改pojo跟xml的工程量太大,可以考虑sql语句使用别名,比如

1
2
3
4
<!--配置查询所有 id为方法名称,将获取到的数据封装到resultType这里面-->
<select id="findAll" resultType="top.meethigher.demo05.domain.Person">
select id as userId, username as userName from fairy
</select>

这样只需要修改sql语句即可。

当然了,上面这种方法还是比较麻烦的,mybatis中提供了查询结果的列名和实体类的属性名对应的resultMap。

此时,执行sql语句的地方,返回值就不能是resultType了,应该用resultMap的id。

1
2
3
4
5
6
7
8
9
10
11
<!--配置查询结果的列名和实体类的属性名的对应关系-->
<resultMap id="personMap" type="top.meethigher.demo05.domain.Person">
<!--主键字段的对应-->
<id property="userId" column="id" javaType="Integer" jdbcType="Integer"></id>
<!--非主键字段的对应-->
<result property="userName" column="username" javaType="String" jdbcType="String"></result>
</resultMap>
<!--配置查询所有 id为方法名称,将获取到的数据封装到resultType这里面-->
<select id="findAll" resultMap="personMap">
select * from fairy;
</select>

这两种xml配置方式优劣对比

  • 前者开发效率低、后者开发效率高
  • 前者运行效率高、后者运行效率低

2.3 Dao实现类编写

以demo6为例,创建PersonDao的实现类PersonDaoImpl,重写内部方法,额外添加的属性和方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PersonDaoImpl implements PersonDao {
private SqlSession sqlSession;
private SqlSessionFactory factory;

//之所以这个地方用sqlsessionfactory是因为在这里面创建的sqlsession可以直接在这里关闭,事务提交也可以直接在这里提交
public PersonDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}

public void init() {
sqlSession = factory.openSession();
}

public void destroy(boolean isCommit) {
if (isCommit) {
sqlSession.commit();
System.out.println("提交事务");
}
sqlSession.close();
}

@Override
... ...
}

具体实现步骤

  1. 调用factory获取sqlSession
  2. 实现类中重写方法,通过sqlSession调用指定方法进行增删改查
  3. 配置文件之类的不用修改

SqlSession的常用增删改查方法

  • selectList(String statement):返回一个列表。传入xml配置中的namespace.id(即增删改查的方法名),可以表示sql的位置

  • selectList(String statement,Object param):返回一个列表。后面的param为执行sql时需要携带的变量,如果不传,会报错xx关键字是null

  • selectOne(String statement):返回一个对象

  • selectOne(String statement,Object param):返回一个列表。selectOne本质上是调用了selectList,然后get(0)

  • insert(String statement,Object param):返回一个整型

  • update(String statement,Object param):返回一个整型

  • delete(String statement,Object param):返回一个整型

寻找接口实现类

细节就要从源码入手了,但是看源码,按住ctrl点击SqlSession找到的是个接口,下面就不知该如何找了。下面介绍方法。

首先在用到sqlSession的地方打断点,然后debug运行测试类

按住ctrl点击SqlSession进入该接口,然后下面三个方法任选其一。

法一

diagrams->show diagrams,选择java-class-diagrams,出来模型之后,再右键addClassToDiagrams,勾选include,添加即可。

法二

直接按快捷键ctrl+H,就会出现

法三

直接点击文件左边的标记

参考教程

通过给实现类findAll跟SqlSession实现类中selectList打断点,可找到Executor的实现类CachingExecutor

源码分析

分析源码前,先分析下PreparedStatement的执行sql增删改查的方法

  1. execute():它能执行CRUD中任意一种语句
  2. executeUpdate():它能执行CUD语句。返回影响行数
  3. executeQuery():它只能执行R语句

Statement中,还有这三个方法的可传参数的重载方法。

以自定义dao实现类的findAll为例

  • 进入dao实现类中调用的selectList源码
  • 进入selectList所在接口的实现类DefaultSqlSession(通过断点查找,参考上文)
  • 查看其中的selectList方法内容
  • 进入其中的query方法,发现是Executor接口
  • 查找进入该接口的实现类CachingExecutor,继续查找调用query,通过断点,可知调用query的是SimpleExecutor实现类,进入该实现类
  • 在该实现类中,发现只有doQuery,没有query,那么我们应该意识到该往上层去找,SimpleExecutor继承了BaseExecutor抽象类,进入该类
  • 除去抽象的方法,我们可以找到所需的query方法,ifelse如果不知道执行的哪条语句,就两条语句打断点,看执行哪一条。通过断点,我们知道执行的是queryFromDatabase,进入该方法,可知又调用了doQuery,因此,我们再返回SimpleExecutor,查看doQuery
  • 继续在doQuery中打断点,可知调用query的是RoutingStatementHandler,进入该类
  • 在RoutingStatementHandler中query方法打断点,继续找到PreparedStatementHandler
  • 在PreparedStatementHandler中,最终可以找到调用查询的方法了。PreparedStatement.execute()。至于封装结果集,其实原理一样,不再做解释了。

如果文字步骤看着头疼,可以参考该视频。查看mybatis自定义实现类底层源码

通过查看源码可以知道,其他的自定义dao实现类最终增删改都是通过PreparedStatement.execute()来实现的。

以mybatis内置动态代理dao实现的findAll为例

  • 进入getMapper方法所在文件,打断点,找到其实现类DefaultSqlSession
  • 继续找getMapper,进入Configuration,继续找,进入MapperRegistry
  • 进入newInstance所在类MapperProxyFactory,发现InvocationHandler的增强实现类是MapperProxy,进入
  • 找到重写方法,不知道代码如何执行的,那就打断点,找到PlainMethodInvoker,会发现调用了execute方法,进入所在文件
  • 会发现有个switch用来判断是否是增删改查,如果是findAll,通过断点可知最后调用了selectList,下面的过程就跟自定义dao的一样了

查看mybatis内置动态代理底层源码

2.4 配置文件的细节

详细配置的属性标签顺序如下图

配置properties

配置数据库连接可以直接使用环境搭建中写死的那种。

也可以在标签内部配置连接数据库的信息,还可以通过属性引用外部配置文件信息,

常用属性

  • resource:用于指定配置文件的位置。必须存在于类路径下(是指编译后的)
  • url:按照url的写法来写地址,不只适用于properties,也适用于mappers
    • URL:uniform resource locator 统一资源定位符,它可以唯一标识一个资源的位置。
    • 写法:http://localhost:8080/1/2,即协议://主机:端口/URI。协议有http、https、file,比如用浏览器打开一个文件,前面会有file:///xxx,就是这个道理。
    • URI:uniform resource identifier 统一资源标识符,它在应用中唯一定位资源。

下面介绍properties的另外两种写法。

法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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>
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/beauty?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="top/meethigher/demo01/dao/PersonDao.xml"/>
</mappers>
</configuration>

法二

在maven的resource根目录下,创建jdbc.properties

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/beauty?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=root

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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>
<properties resource="jdbc.properties"/>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="top/meethigher/demo01/dao/PersonDao.xml"/>
</mappers>
</configuration>

说实话,我实在没感觉出来这两种写法的好处

配置typeAliases

像xml中配置简单类型时,不区分大小写,而配置全类名时,严格区分大小写。如果不想区分的话,可以使用别名。

使用typeAliases配置别名,就可以直接使用别名访问了,使用别名就不会区分大小写了。

两个常用属性

  • type:实体类的全限定类名
  • alias:别名
1
2
3
4
5
6
7
8
<configuration>
<typeAliases>
<typeAlias type="top.meethigher.demo06.domain.Person" alias="person"></typeAlias>
</typeAliases>
<!--配置环境-->
<environments default="mysql"></environments>
<mappers></mappers>
</configuration>

如果内容太多,写type全名也麻烦,typeAliases提供了package标签,用于指定实体类的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写。

1
2
3
4
5
6
7
8
<configuration>
<typeAliases>
<package name="top.meethigher.demo06.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql"></environments>
<mappers></mappers>
</configuration>

在mappers中,也有package标签,他是用来指定接口所在包,当指定之后,就不需要写带有resource或者class属性的mapper了。

1
2
3
4
5
6
7
8
9
10
<configuration>
<typeAliases>
<package name="top.meethigher.demo06.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql"></environments>
<mappers>
<package name="top.meethigher.demo06.dao"/>
</mappers>
</configuration>

三、Mybatis的深入和多表

3.1 连接池

我们在实际开发中,都会使用连接池,因为它可以减少我们获取连接所消耗的时间。

连接池就是用与存储连接的一个容器,容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到一个同一连接。

该集合还必须实现队列的特性:先进先出。

Mybatis中的连接池,提供了三种方式的配置

  • 配置的位置:主配置文件中的dataSource标签,type属性就是表示采用何种连接方式。
  • type属性的取值
    • POOLED:采用java.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
    • UNPOOLED:采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource接口,但是并没有使用池的思想,也就是每次用都重新获取一个新的连接
    • JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到的dataSource是不一样的。
      • 注意:如果不是web或者maven的war工程,是不能使用的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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>
<typeAliases>
<package name="top.meethigher.demo07.domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/beauty?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="top.meethigher.demo07.dao"/>
</mappers>
</configuration>

POOLED与UNPOOLED的区别如下图所示

数据库连接池中,获取连接的方式

我一直不明白,数据库连接池中的close为啥是归还,而不是关闭,通过查看源码,发现调用的就是驱动提供的close方法,但是在mybatis中的PooledConnection,我又发现有个真实Connection和代理Connection,而实际关闭的是代理Connection,所以说是归还?不懂。。

后来有个大佬解决了我的问题,我一听就觉得有道理!

我总结了思路,放到了b站

我也赶紧拿着我刚学会的知识,分享给大佬

3.2 事务控制

在传统的JDBC中,可以通过Connection.setAutoCommit(true),来设置事务的自动提交。

在mybatis中,对JDBC进行了封装,所以mybatis的事务控制本身也是用JDBC的setAutoCommit()来设置事务提交方式的。

在mybatis中,SqlSessionFactory.openSession(boolean autoCommit),是可以传入参数的,当然也可以像之前一样,不传参数。

如果我们传入了参数true,那么就会自动提交事务了,不需要手动提交。但是在实际开发中,通常不会设置为自动提交,而是根据实际情况再进行手动提交。

3.3 映射文件的动态SQL

if标签

如果在查询时,直接传入了一个Person对象,Person对象中如果有属性不为空,就在sql中携带该属性的值,进行查询。后面携带1=1是为了防止参数都为NULL时的错误。

1
2
3
4
5
6
7
8
9
10
11
12
<select id="findByCondition" parameterType="Person" resultType="Person">
select * from fairy 1=1
<if test="name != null">
and name=#{name}
</if>
<if test="gender!=null">
and gender=#{gender}
</if>
<if test="age!=null">
and age=#{age}
</if>
</select>

where标签

如果不想携带1=1,可以用where标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findByCondition" parameterType="Person" resultType="Person">
select * from fairy
<where>
<if test="name != null">
and name=#{name}
</if>
<if test="gender!=null">
and gender=#{gender}
</if>
<if test="age!=null">
and age=#{age}
</if>
</where>
</select>

foreach标签

那么在mybatis像下面这种语句应该如何实现呢

1
select * from fairy where id in (1,2,3);

在mybatis中,提供了foreach标签,用于遍历集合,它的属性如下

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句开始部分
  • close:代表语句结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符

示例

1
2
3
4
5
6
7
8
9
10
11
<!--根据QueryVo中的id集合,查询-->
<select id="findByIds" parameterType="QueryVo" resultType="Person">
select * from fairy
<where>
<if test="ids!=null and ids.size()>0">
<foreach collection="ids" open="and id in(" close=")" item="id" separator=",">
${id}
</foreach>
</if>
</where>
</select>

抽取重复语句

直接看最终示例吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="top.meethigher.demo08.dao.PersonDao">
<!-- 抽取出其中重复的sql语句,设置id,通过include标签的refid属性引入 -->
<sql id="defaultPerson">
select * from fairy
</sql>
<select id="findAll" resultType="Person">
<include refid="defaultPerson"></include>
</select>
<select id="findPersonById" parameterType="Integer" resultType="Person">
<include refid="defaultPerson"></include>
where id=#{id};
</select>
</mapper>

注意

抽取的代码片段,不要带分号

3.4 多表查询

表之中的关系,有三种

  • 一对多(多对一):一个单位有多个员工,多个员工属于一个单位
  • 一对一:一个员工对应一个身份证号,一个身份证号只能属于一个人
  • 多对多:一个学生有多个老师,一个老师教多个学生

特例:如果任远一个员工,他都是属于一个单位,在mybatis中,这种多对一,视作一对一。

实现一对一、一对多

下面用mybatis来实现多表查询,通过用户和账户的关系(一个用户有多个账户,一个账户对应一个用户)

步骤

  1. 建立两张表,user表、account表,让user表和account表之间具备一对多的关系
    • 需要使用外键在账户表中添加
  2. 建立两个实体类,user实体类,account实体类,让user实体类和account实体类之间体现出一对多的关系
    • 从表实体应该包含一个主表实体的对象引用,说白了,就跟上面综合查询中,实现一对一关系一样,实际是内连接
    • 主表实体应该包含从表实体的集合引用,说白了,就是查询出主表的所有数据,以及跟主表有关系的从表集合,实际是外连接
  3. 建立两个配置文件,userdao配置文件、accountdao配置文件
  4. 实现配置
    • 当我们查询user时,可以同时得到user下所包含的account信息
    • 当我们查询account时,可以同时得到account的所属user信息。

根据数据架构图,创建响应的pojo实体类。

先讲一个不常用的方式,pojo里面包含另一个pojo的属性

比如,在查询账户时,同时获取用户的姓名和地址,那我们可以通过创建一个AccountUser类,继承Account,同时额外添加两条属性姓名和地址。

1
2
3
4
5
6
public class AccountUser extends Account {
private String username;
private String address;

//getter、setter、toString
}

然后在相应的映射配置文件AccountDao.xml中

1
2
3
4
5
6
7
8
9
<?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="top.meethigher.demo09.dao.AccountDao">
<select id="findAllAccount" resultType="AccountUser">
select a.*,u.username,u.address from account a,user u where a.uid=u.uid;
</select>
</mapper>

运行结果

注意

上面这个方法并不常用

一对一关系

常用的方法,就跟综合查询一样,pojo里面包含pojo。

既然,查询账户时,要把用户信息也获取到,那我们在账户pojo里面,添加一个用户的pojo对象作为属性不就OK了。

Account.java

1
2
3
4
5
6
7
8
9
public class Account {
private int aid;
private int uid;
private double money;
//添加一对一关系映射:从表实体包含一个主表实体的对象引用
private User user;

//getter、setter、toString
}

他的映射AccountDao.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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="top.meethigher.demo09.dao.AccountDao">
<resultMap id="accountUser" type="Account">
<id property="aid" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--建立一对一关系映射 property对应Account中的user属性,column对应外键对应的列,后面的属性不填,mybatis也会智能识别-->
<association property="user" column="uid" javaType="User">
<id column="uid" property="uid"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
</association>
</resultMap>
<select id="findAll" resultMap="accountUser">
select * from user,account where user.uid=account.uid;
</select>
</mapper>

如果对于association的column不理解,也没事,因为他是为了懒加载准备的,可以跳到第五章。

一对多关系

pojo里面包含pojo列表

User.java

1
2
3
4
5
6
7
8
9
10
11
public class User {
private int uid;
private String username;
private String birthday;
private String sex;
private String address;
//添加一对多关系映射:主表实体应该包含从表实体的集合引用
private List<Account> accounts;

//setter、getter、toString
}

他的映射UserDao.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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="top.meethigher.demo09.dao.UserDao">
<resultMap id="userAccount" type="User">
<id property="uid" column="uid"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<!--配置一对多关系映射,采用集合接收数据 ,ofAccount表示集合的类型-->
<collection property="accounts" ofType="Account">
<id property="aid" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userAccount">
select * from user left outer join account on user.uid=account.uid;
</select>
</mapper>

注意

实际执行上面左外连接sql时,是有重复的user数据的,在mybatis中,通过技术的手段过滤掉了。

实现多对多

步骤

  1. 建立两张表,people表、role表,让people表和role表之间具备多对多的关系
    • 在数据库中实现多对多,需要使用中间表
    • 中间表中包含以上两张表的主键,分别作为外键,也作为本表的联合主键
  2. 建立两个实体类,people实体类,role实体类,让people实体类和role实体类之间体现出多对多的关系
    • 各自包含一个对方的集合引用
  3. 建立两个配置文件,persondao配置文件、roledao配置文件
  4. 实现配置
    • 当我们查询people时,可以同时得到该people所包含的role信息
    • 当我们查询role时,可以同时得到role的所属people信息。

创建Role实体类

1
2
3
4
5
6
7
8
9
public class Role {
private String rid;
private String rolename;
private String roledesc;
//一对多关系映射
private List<People> peoples;

// getter、setter、toString
}

映射配置文件RoleDao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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="top.meethigher.demo09.dao.RoleDao">
<resultMap id="rolePeople" type="Role">
<id property="rid" column="rid"></id>
<result property="rolename" column="rolename"></result>
<result property="roledesc" column="roledesc"></result>
<collection property="peoples" ofType="People">
<id property="pid" column="pid"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="rolePeople">
select * from role
left outer join people_role on people_role.rid=role.rid
left outer join people on people_role.pid=people.pid;
</select>
</mapper>

创建People实体类

1
2
3
4
5
6
7
8
9
10
11
public class People {
private int pid;
private String username;
private String birthday;
private String sex;
private String address;
//一对多关系映射
private List<Role> roles;

//getter、setter、toString
}

映射配置文件PeopleDao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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="top.meethigher.demo09.dao.PeopleDao">
<resultMap id="peopleRole" type="People">
<id property="pid" column="pid"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
<collection property="roles" ofType="Role">
<id property="rid" column="rid"></id>
<result property="rolename" column="rolename"></result>
<result property="roledesc" column="roledesc"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="peopleRole">
select * from people
left outer join people_role on people_role.pid=people.pid
left outer join role on people_role.rid=role.rid;
</select>
</mapper>

不管一对多,还是多对多,使用外连接,都是比较麻烦的。

有更简便的方法,参照下一章的懒加载。

四、JNDI

4.1 概念

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口。

JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。

目的是模仿windows系统中的注册表,属于JavaEE技术之一,只支持web工程。

在windows中,输入regedit,就能进入注册表编辑器,在windows中,本质上是按键值对存储的

  • 键:属性的完整路径,因为属性有重复,所以带有绝对路径
  • 值:数据

jndi同理,tomcat服务器启动,jndi就会创建类似的map对象。

  • 值:directory+name,directory是固定的,name是指定的(在META-INF的目录下右context.xml中指定name)
  • 值:对象,通过配置文件的方式指定对象

4.2 实现

步骤

  1. 通过idea创建maven工程,使用maven-webapp骨架创建,添加pom中servlet跟jsp,配置tomcat服务器
  2. 添加主配置文件mybatis.xml,在webapp下创建META-INF的文件夹,下面创建context.xml
  3. 在java下引入之前的dao跟domain,resources下引入响应的dao映射配置文件
  4. 将测试类代码,通过java代码块的方式引入jsp中
  5. 通过idea编辑运行配置为本地tomcat,不用maven运行

主配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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>
<typeAliases>
<package name="top.meethigher.domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="JNDI">
<!-- java:comp/env是固定的写法,后面的jdbc/mybatis是自定义的 -->
<property name="data_source" value="java:comp/env/jdbc/mybatis"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="top.meethigher.dao"/>
</mappers>
</configuration>

至于META-INF下的context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- type、auth都是固定的,auth的Container表示容器运行,指Tomcat -->
<Resource
name="jdbc/mybatis"
type="javax.sql.DataSource"
auth="Container"
maxActive="20"
maxWait="10000"
maxIdle="5"
username="root"
password="root"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"
/>
</Context>
发布:2021-01-25 00:41:20
修改:2021-04-10 15:00:23
链接:https://meethigher.top/blog/2021/mybatis/
标签:sql mybatis 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏