Mybatis是什么?有什么特点?
它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低
- 什么是ORM?
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据
- 为什么mybatis是半自动的orm框架
由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。
- 编写全局配置文件
- 编写mapper映射文件
- 加载全局配置文件,生成SqlSessionFactory
- 创建SqlSession,调用mapper映射文件中的SQL语句来执行CRUD操作
事例:
数据库创建表 创建maven项目 在父工程中倒入依赖的jar包创建一个pojo实体类mysql mysql-connector-java 8.0.26 org.mybatis mybatis 3.5.2 junit junit 4.12 test org.projectlombok lombok 1.18.12 provided
package com.kang.pojo;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class user {
private int id;
private String name;
private String pwd;
private Integer score;
private Integer gender;
}
编写mapper映射文件(编写SQL)
编写properties文件select * from USER insert into user (name,score,gender,id,pwd)value (#{name}.#{score},#{gender},#{id},#{pwd}); delete from user where id = #{id};
db.url = jdbc:mysql://localhost:3306/myBatis db.user = root db.pwd = 12345678 db.driver = com.mysql.cj.jdbc.Driver编写全局配置文件
编写dao类
package com.kang.dao;
import com.kang.pojo.user;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class StudentDao {
private SqlSessionFactory sqlSessionFactory;
public StudentDao(String configPath) throws IOException {
InputStream inputStream = Resources.getResourceAsStream(configPath);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public List findAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List studentList = sqlSession.selectList("findAll");
sqlSession.close();
return studentList;
}
public int addStudent(user user) {
SqlSession sqlSession = sqlSessionFactory.openSession();
int rowsAffected = sqlSession.insert("insert", user);
sqlSession.commit();
sqlSession.close();
return rowsAffected;
}
public int deleteStudent(int id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
int rowsAffected = sqlSession.delete("delete",id);
sqlSession.commit();
sqlSession.close();
return rowsAffected;
}
}
测试
package com.kang.dao;
import com.kang.pojo.user;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class SimpleTest {
private StudentDao studentDao;
@Before
public void init() throws IOException {
studentDao = new StudentDao("mybatis-config.xml");
}
@Test
public void insertTest() {
user user = new user();
user.setName("yogurt");
user.setId(4);
user.setGender(1);
user.setScore(100);
user.setPwd("77777777");
studentDao.addStudent(user);
}
@Test
public void findAllTest() {
List all = studentDao.findAll();
for (user user : all) {
System.out.println(user.toString());
}
}
}
结果展示
-
在编写mapper映射文件时,select和insert中resultType和parameterType区别:
-
使用resultType :主要针对于从数据库中提取相应的数据出来
-
使用parameterType :主要针对于将信息存入到数据库中
-
resultType:
- 基本数据类型
- pojo类类型。mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果对有多条数据,则分别映射,并把对象放到list容器中。所以即使返回时list数组,resultType也是pojo类型
-
parameterType:
- 基本数据类型
- 复杂数据类型(如类和Map)
-
-
关于注解开发,在测试类中没有识别到Lombok的@Getter和@Setter
- 处于idea的问题,问题汇报并不影响程序的执行,他没有识别到而已并不代表我们没有
- 可以配置idea让他对于这个问题不报错(不推荐)
-
关于静态资源过滤问题:
在平常我们编写的时候可能会遇到在target中找不到对应的文件,我们有两种方式来解决
-
在子项目的pom.xml中加入静态资源过滤
src/main/java ***.xml false src/main/resources ***.xml false -
在resource目录下添加相同路径的包,然后将映射文件放到这个包的路径之下
-
为了方便理解,将测试流程中的全局配置文件拿来
这个之中只有简单的三个,,,其实还有好多,但是目前还没有接触到,我们要严格遵守他定义的顺序,那么接下来解析一下每个标签的作用
自标签的说明 properties这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(注意,这里的代码个人理解的意思为自定义properties里的内容,他的优先级要高于在文件中的初始定义,在使用时自动覆盖掉源文件中的对应信息)
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。
我们一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入后,在下面的environment标签中,就可以用${}占位符快速获取数据源信息
mybatis家在配置的顺序
首先读取在properties元素体中的指定的属性
然后根据properties元素中的resource属性读取类路径下的属性文件,或根据url属性指定的路径读取属性文件,并覆盖掉之前读取的同名属性
最后读取作为方法参数传递的属性,并覆盖掉之前的同名属性
settings这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。用来开启或关闭mybatis的一些特性,比如可以用
大致浏览:
展示一下配置完整的settings元素的示例
typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
这样做的目的是为了简化我们使用全名称地址,在任何我们需要用到全名称地址的时候我们可以使用他的alias别名
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
typeHandlers
用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。这个标签用的不多
objectFactorymybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多
plugins可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。在mybatis底层,运用了责任链模式+动态代理去实现插件的功能
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
提示 覆盖配置类
除了用插件来修改 MyBatis 核心行为以外,还可以通过完全覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会极大影响 MyBatis 的行为,务请慎之又慎。
environments用来配置数据源,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景,==尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。==所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个SqlSessionFactory。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单
- 每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么将会加载默认环境,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
注意一些关键点:
- 默认使用的环境 ID(比如:default=“development”)。
- 每个 environment 元素定义的环境 ID(比如:id=“development”)。
- 事务管理器的配置(比如:type=“JDBC”)。
- 数据源的配置(比如:type=“POOLED”)。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
mapper用来配置mapper.xml映射文件,这些xml文件里都是SQL语句,举个简单的例子,在笔记的开头我们的示例中
mapper就是注册这个文件然后进行使用的
对于简单类型(8种java原始类型再加一个String)的入参,${}中参数的名字必须是value,例子如下
为什么简单类型的变量名必须为value呢?因为mybatis源码中写死的value,哈哈
上面其实是比较原始的开发方式,我们需要编写dao类,针对mapper.xml中的每个SQL标签,做一次封装,SQL标签的id要以字符串的形式传递给SqlSession的相关方法,容易出错,非常不方便;为了简化开发,mybatis提供了mapper接口代理的开发方式,不需要再编写dao类,只需要编写一个mapper接口,一个mapper的接口和一个mapper.xml相对应,只需要调用SqlSession对象上的getMapper(),传入mapper接口的class信息,即可获得一个mapper代理对象,直接调用mapper接口中的方法,即相当于调用mapper.xml中的各个SQL标签,此时就不需要指定SQL标签的id字符串了,mapper接口中的一个方法,就对应了mapper.xml中的一个SQL标签
XML映射文件 示例流程创建mapper接口
public interface StudentMapper {
List findAll();
int insert(Student student);
int delete(Integer id);
List findByName(String value);
}
编写mapper.xml文件
当然不能忘记我们的实体类
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Student{
private String name;
private Integer age;
}
编写MyBatis-config.xml
前提是我们db.properties文件
db.url = jdbc:mysql://localhost:3306/myBatis db.user = root db.pwd = 12345678 db.driver = com.mysql.cj.jdbc.Driver
然后是测试类
public class MapperProxyTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("MyBatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List studentList = mapper.findAll();
for (Student student : studentList) {
System.out.println(student.toString());
}
List students = mapper.findByName("张三");
for (Student student : students) {
System.out.println(student.toString());
}
try {
Student student = new Student();
student.setName("老六");
student.setAge(1);
mapper.insert(student);
sqlSession.commit();
sqlSession.close();
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
要点
- 里面有一些非常要注意的点,我们之前在dao层曾经写过
这两个语句在简化后没有了,所以我们有两种方式去做,第一种是在测试文件里加入sqlSession.commit()和sqlSession.close(),另外一种方式是openSession改为true
SqlSession sqlSession = sqlSessionFactory.openSession(true);
openSession默认自动提交事务关闭,我们需要手动打开
- mapper接口的全限定名,要和mapper.xml的namespace属性一致
- mapper接口中的方法名要和mapper.xml中的SQL标签的id一致
- mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致
- mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致
这些是我们需要注意的属性
insert, update 和 delete selectKey 基于注解的开发这个操作其实并不是很适合大量sql代码的开发,不过对于目前而言简单的sql我们可以利用注解来进行开发,而不用去xml里进行编写,但是这也意味着后续的维护和拓展性不回很好(如果想修改SQL语句,就得改代码,得重新打包部署,而如果用xml方式,则只需要修改xml,用新的xml取替换旧的xml即可)
使用注解的开发方式,也还是得有一个全局配置的xml文件,不过mapper.xml就可以省掉了,具体操作只用2步
基本流程-
创建一个Mapper接口
public interface Mapper { @Select("SELECT * FROM student") ListfindAll(); @Insert("INSERT INTO student (name,age) VALUES (#{name},#{age})") int insert(Student student); } -
当然实体类必不可少,但是这里就不代入了
-
db.properties文件也是需要的
-
编写全局配置文件(不是pom.xml)
-
测试类
public class PureMapperTest { private SqlSessionFactory sqlSessionFactory; @Before public void init() throws IOException { InputStream inputStream = Resources.getResourceAsStream("MyBatis-config.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void test() { SqlSession sqlSession = sqlSessionFactory.openSession(); Mapper mapper = sqlSession.getMapper(Mapper.class); mapper.insert(new Student("Tomcat",10)); sqlSession.commit(); ListstudentList = mapper.findAll(); for (Student student : studentList) { System.out.println(student.toString()); } } } 运行结果
如果我们需要传入多个参数,那么可以加入注解==@Param==
public interface PureStudentMapper {
@Select("SELECT * FROM student WHERe name like '%${name}%' AND major like '%${major}%'")
List find(@Param("name") String name, @Param("major") String major);
}
@Param标签会被mybatis处理并封装成一个Map对象,比如上面的示例中,实际传入的参数是一个Map对象,@Param标签帮忙向Map中设置了值
注意要点刚刚在编写全局配置文件的时候遇到一个问题就是注册mapper接口的时候一直失败,到底是为什么呢?
其实很简单,我们的类型选择错误了。
这个其实还好说,但是以后在工作中遇到问题更加复杂
在实际工作中,一般我们会将一张表的SQL操作封装在一个mapper.xml中,可能有许多张表需要操作,那么我们是不是要在
那么目前就总结出来三种加载mapper的方法
-
加载普通的xml文件,传入xml的相对路径(相对于类路径)
-
使用mapper接口的全限定名来加载,若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一个目录
-
扫描指定包下的所有mapper,若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一目录
可以根据具体的参数条件,来对SQL语句进行动态拼接。
比如在以前的开发中,由于不确定查询参数是否存在,许多人会使用类似于where 1 = 1 来作为前缀,然后后面用AND 拼接要查询的参数,这样,就算要查询的参数为空,也能够正确执行查询,如果不加1 = 1,则如果查询参数为空,SQL语句就会变成SELECt * FROM student where,SQL不合法。
mybatis里的动态标签主要有这些
ifSELECT * FROM student WHERe age >= 18 AND name like '%${name}%'
当满足test条件时,才会将
SELECT * FROM BLOG WHERe state = ‘ACTIVE’ AND title like #{title} AND author_name like #{author.name} AND featured = 1
有一些像java的switch
whereSELECT * FROM BLOG state = #{state} AND title like #{title} AND author_name like #{author.name}
set...
在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加SET,并且如果SET之后是以,开头的话,会自动将其删掉
...
可以通过
实际上在mybatis源码,也能看到trim与set,where标签的父子关系
foreach用来做迭代拼接的,通常会与SQL语句中的IN查询条件结合使用,注意,到parameterType为List(链表)或者Array(数组),后面在引用时,参数名必须为list或者array。如在foreach标签中,collection属性则为需要迭代的集合,由于入参是个List,所以参数名必须为list
SELECT * FROM student WHERe id in #{item}
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。
sqlSELECT * FROM POST P WHERe ID in #{item}
可将重复的SQL片段提取出来,然后在需要的地方,使用
bindSELECT * FROM user AND username like '%${user.name}%'
mybatis的动态SQL都是用OGNL表达式进行解析的,如果需要创建OGNL表达式以外的变量,可以用bind标签
缓存 一级缓存SELECT * FROM BLOG WHERe title LIKE #{pattern}
默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库
一级缓存在下面情况会被清除
- 在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存
- SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
- 对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在mybatis中会被封装成一个MappedStatement)
- 在全局配置文件中设置
,这样会使一级缓存失效,二级缓存不受影响
默认关闭,可通过全局配置文件中的开启二级缓存总开关,然后在某个具体的mapper.xml中增加,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中
详细MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可以用到的清除策略:
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
关联查询使用
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
基于 JavaBean 的规范,上面这个类有 3 个属性:id,username 和 hashedPassword。这些属性会对应到 select 语句中的列名。
这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。
select id, username, hashedPassword from some_table where id = #{id}
在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。比如:
select user_id as "id", user_name as "userName", hashed_password as "hashedPassword" from some_table where id = #{id}
在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。
然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
select user_id, user_name, hashed_password from some_table where id = #{id}
下面介绍一些常见的resultMap外部配置的标签
他的属性:
id和result这些元素是结果映射的基础。id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
两个元素都有一些属性:
关联(多对一)关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
- 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
- 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
首先,先让我们来看看这个元素的属性。你将会发现,和普通的结果映射相比,它只在 select 和 resultMap 属性上有所不同。
不推荐,因为在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:
- 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
- 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。
关联查询的结果集映射现在我们可以映射这个结果:
在上面的例子中,你可以看到,博客(Blog)作者(author)的关联元素委托名为 “authorResult” 的结果映射来加载作者对象的实例。
非常重要: id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。
现在,上面的示例使用了外部的结果映射元素来映射关联。这使得 Author 的结果映射可以被重用。 然而,如果你不打算重用它,或者你更喜欢将你所有的结果映射放在一个具有描述性的结果映射元素中。 你可以直接将结果映射作为子元素嵌套在内。这里给出使用这种方式的等效例子:
集合(一对多)
集合元素和关联元素几乎是一样的,它们相似的程度之高,以致于没有必要再介绍集合元素的相似部分。 所以让我们来关注它们的不同之处吧。
我们来继续上面的示例,一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示
private Listposts;
要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。 和关联元素一样,我们可以使用嵌套 Select 查询,或基于连接的嵌套结果映射集合。
集合的嵌套 Select 查询首先,让我们看看如何使用嵌套 Select 查询来为博客加载文章。
SELECT * FROM BLOG WHERe ID = #{id} SELECT * FROM POST WHERe BLOG_ID = #{id}
你可能会立刻注意到几个不同,但大部分都和我们上面学习过的关联元素非常相似。 首先,你会注意到我们使用的是集合元素。 接下来你会注意到有一个新的 “ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。 所以你可以按照下面这样来阅读映射:
读作: “posts 是一个存储 Post 的 ArrayList 集合”
在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写。所以很多时候你可以简略成:
集合的嵌套结果映射
现在你可能已经猜到了集合的嵌套结果映射是怎样工作的——除了新增的 “ofType” 属性,它和关联的完全相同。
首先, 让我们看看对应的 SQL 语句:
select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, P.id as post_id, P.subject as post_subject, P.body as post_body, from Blog B left outer join Post P on B.id = P.blog_id where B.id = #{id}
我们再次连接了博客表和文章表,并且为每一列都赋予了一个有意义的别名,以便映射保持简单。 要映射博客里面的文章集合,就这么简单:
再提醒一次,要记得上面 id 元素的重要性,如果你不记得了,请阅读关联部分的相关部分。
以上内容大部分来自官方文档还有个人学习笔记,主要涉及到的点是我们作为新手会经常遇到的问题还有需要掌握的知识点 (个人认为)。一点也不辛苦主要是为了日后重新回顾用的哈哈哈希望大家不要点赞,如果有兴趣交流的小伙伴可以私信我的微信:H-20200406



