摘要
之前学校教的mybatis感觉有点肤浅了,这不是有时间吗,b站自学一波!
正文
目录
Mybatis的CRUD Mybatis的缓存及注解开发 源码 学一样东西,当然是先看官网文档 啦!
什么是框架?
框架是软件开发中一套解决方案,不同的框架解决的是不同的问题。
使用框架的好处?
框架封装了很多细节,使开发者可以使用极简的方式实现功能,大大提高开发效率。
什么是三层架构?
表现层:用于展示数据的 业务层:处理业务需求的 持久层:和数据库交互的 像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 环境搭建 步骤
创建Maven工程导入依赖坐标
创建实体类Person和实体的接口PersonDao
创建Mybatis的主配置文件:SQLMapConfig.xml
配置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&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&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实现的。
步骤
读取配置文件 创建SqlSessionFactory工厂 创建SqlSession 创建Dao接口的代理对象(动态代理) 执行Dao接口中的方法 释放资源 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>
整个结构
展开
环境搭建注意事项
创建PersonDao.java和PersonDao.xml,在Mybatis中把持久层的操作接口名称的映射文件也叫做Mapper idea创建directory和package不一样。直接通过xx.xx.xx创建,如果directory,则创建出的是一级目录,如果是package,则创建出的是三级目录 mybatis的映射配置必须和dao接口的包结构相同 映射配置文件mapper的namespace标签属性的值必须是dao接口的全类名 映射配置文件的操作配置为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对象的方式做的两件事
创建代理对象,Dao的代理实现类 调用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 );
}
这个地方的我写的可能有点问题,如果实在看不明白,就直接跳到**多表查询 **
返回值 返回值有两种
返回值Null问题 在使用过程中,如果pojo对象如果mysql中的属性不对应,会导致返回的数据无法存储。
比如
实体 主键属性 普通属性 数据库 id username pojo类 userId userName
执行插入更改删除之类的,是没问题的,但如果是执行返回数据,像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
... ...
}
具体实现步骤
调用factory获取sqlSession 实现类中重写方法,通过sqlSession调用指定方法进行增删改查 配置文件之类的不用修改 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增删改查的方法
execute():它能执行CRUD中任意一种语句 executeUpdate():它能执行CUD语句。返回影响行数 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,也适用于mappersURL: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&useUnicode=true&characterEncoding=UTF-8&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配置别名,就可以直接使用别名访问了,使用别名就不会区分大小写了。
两个常用属性
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&useUnicode=true&characterEncoding=UTF-8&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来实现多表查询,通过用户和账户的关系(一个用户有多个账户,一个账户对应一个用户)
步骤
建立两张表,user表、account表,让user表和account表之间具备一对多的关系 建立两个实体类,user实体类,account实体类,让user实体类和account实体类之间体现出一对多的关系从表实体应该包含一个主表实体的对象引用 ,说白了,就跟上面**综合查询 中,实现一对一关系一样,实际是 内连接**主表实体应该包含从表实体的集合引用 ,说白了,就是查询出主表的所有数据,以及跟主表有关系的从表集合,实际是外连接 建立两个配置文件,userdao配置文件、accountdao配置文件 实现配置当我们查询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中,通过技术的手段过滤掉了。
实现多对多 步骤
建立两张表,people表、role表,让people表和role表之间具备多对多的关系在数据库中实现多对多,需要使用中间表 中间表中包含以上两张表的主键,分别作为外键,也作为本表的联合主键 建立两个实体类,people实体类,role实体类,让people实体类和role实体类之间体现出多对多的关系 建立两个配置文件,persondao配置文件、roledao配置文件 实现配置当我们查询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 实现 步骤
通过idea创建maven工程,使用maven-webapp骨架创建,添加pom中servlet跟jsp,配置tomcat服务器 添加主配置文件mybatis.xml,在webapp下创建META-INF的文件夹,下面创建context.xml 在java下引入之前的dao跟domain,resources下引入响应的dao映射配置文件 将测试类代码,通过java代码块的方式引入jsp中 通过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&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
/>
</Context>