对原生jdbc的总结
jdbc-preparestatement程序
public class JDBCTest{
public static void main(String[] args){
Connection connection = null;
PreparedStatement prepareStatement = null;
ResultSet resultSet = null;
try{
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_name?");
String sql = "select * from user where username = ?";
PreparedStatement = connection.prepareStatement(sql);
PreparedStatement.setString(1,"marry");
resultSet = prepareStatement.excuteQuery();
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"))
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(resultSet!=null){
try{
resultSet.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(prepareStatement!=null){
try{
prepareStatement.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(connection!=null){
try{
connection.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
原生jdbc缺点小结
- 数据库操作时,频繁建立或释放连接,造成数据库资源浪费,影响性能(应该被连接池管理)
- SQL语句硬编码不能复用(应该将sql配置在xml文件中)
- PreparedStatement语句,上同
框架mybatis
什么是mybaits
- mybatis是ORM框架,apache下的项目
- mybatis托管在goolecode下,之后托管在github(至今)
- mybatis提供映射方式,但需要程序程序员提供SQL语句
- mybatis可以将输入参数向PreparedStatement进行自动的输入映射,并将结果集自动输出映射为Java对象
- mybatis核心:
mybatis可以将输入参数向preparement中自动进行注入(输入映射)
mybatis将查询结果集映射成java对象(输出映射)
框架mybaits的执行过程
- 配置mybatis的配置文件SqlMapConfig.xml(名字不固定)
- 通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂(SqlSessionFactory在实际使用时按照单例方式使用)
- 通过SqlSessionFactory创建SqlSession
- SqlSession是一个面向用户的接口(提供操作做数据的方法),实现对象昂是线程不安全的,建议SqlSession应用场合在方法体内(可以线程安全,不冲突)
- 释放资源,关闭SqlSession
怎么用mybaits
mybatis的配置文件SqlMapConfig.xml(全局配置文件)
配置了数据源(第三方连接池)、事务(spring-tx)等mybatis运行环境
配置映射文件User.xml
↓
- SqlSessionFactory(会话工厂):创建SqlSession
↓
- SqlSession(会话):面向程序员的接口,操作数据库(CRUD)
↓
- Executor(执行器):面向基本执行器/缓存执行器的接口,SqlSession内部通过执行器操作数据库
↓
- mapped statement(底层封装对象):对操作数据库存储封装(包括sql语句、输入的参数、输出的结果类型)
↓
- mysql
使用mybatis框架开发dao
原始dao开发方法(需要编写dao和daoImpl)
- 存在大量的模版方法(冗余代码)
- SqlSession的statement的部分有硬编码
- SqlSession的statement的部分有泛型参数,使得编译阶段不会报错
mapper代理开发方法(mapper接口/dao接口)
- xxMapper.java接口开发
package cn.wy.dao;
public interface UserMapper {
//若参数名字不一样,可以用@Param指定
User findUserById(@Param("uniqueId") int id);
}
- 需要写UserMapper.xml映射文件
<mapper namespace="cn.wy.dao.UserMapper">
<select id="findUserById" parameterType="int" resultType="cn.wy.domain.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
- mapper.xml中namespace就是mapper.java的类全路径
- mapper.xml中statement的id和mapper.java中方法名一致
- mapper.xml中statement的parameterType值和mapper.java的输入参数类型一致
- mapper.xml中statement的resultType值和mapper.java的方法返回值类型一致
配置_mybatisConfig.xml详解(全局配置)
配置中的加载顺序
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- plugins
- environments
- mappers
配置中加载属性的顺序
- 首先读取在properties中的元素里定义的属性值
- 然后读取在properties中resource/url的属性值(若重复则覆盖)
- 最后读取parameterType传递的属性值(若重复则覆盖)
- 建议:
- 不要在properties元素体内添加任何属性值
- 将属性值全部定义在db.properties中,且属性名需要一定的特殊性
全局参数配置setting
- 比如开启二级缓存、开启延迟加载
- 比如,以下全部:
在mybaits中,setting的的配置参数如下(如果不在配置文件中配置将使用默认值):
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关 | true | false |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态 | true | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true | false |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true | false |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true | false |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true | false |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
defaultFetchSize | Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting. | Any positive integer | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true | false |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true | false |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION | STATEMENT |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | Any String | Not set |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB | JAVASSIST |
别名typeAliases
- 在UserMapper.xml中,定义parameterType或者resultType时需要输入映射类型的全路径,这样十分不方便
- 因此可以针对parameterType或者resultType指定的类型定义一些别名,然后在UserMapper.xml中使用别名方便开发
- typeAlias中有两种写法(一种针对单个别名定义||一种从指定包路径下批量找类名)
<typeAliases>
<typeAlias type="" alias=""/>
<package name="cn.wy.domain"/>
</typeAliases>
- mybatis支持的别名(以下为默认,针对POJO需要自定义)
别名 映射类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean blooean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
Boolean Boolean
date Date
decimal BigDecimal
bigDecimal BigDecimal
类型处理器typeHandlers
- mybatis中通过typeHandlers完成jdbc类型和java类型的转换、
- mybatis支持的已经够用,不必自定义
- 没懂?
mappers(映射配置)
通过resource加载单个映射文件
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
通过mapper接口加载
<mappers>
<mapper class="cn.wy.mapper.UserMapper"/>
</mappers>
需要遵循规范:
将mapper接口类名和mapper.xml映射文件名保持一致,且在同目录下
批量加载多个mapper (推荐使用)
<mappers>
<package name="cn.wy.mapper "/>
</mappers>
完整版mybatisConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"><!-- 1属性(一般不建议在这里写任何属性) -->
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
</properties>
<settings><!-- 2全局配置参数 -->
<setting name="logImpl" value="SLF4J"/>
<setting name="cacheEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<typeAliases><!-- 3类型别名 -->
<package name="cn.wy.domain"/>
</typeAliases>
<typeHandlers><!-- 4类型处理器 -->
<typeHandler
javaType="tk.mybatis.simple.type.Enabled"
handler="tk.mybatis.simple.type.EnabledTypeHandler"/>
</typeHandlers>
<objectFactory><!-- 5对象工厂 -->
asd
</objectFactory>
<plugins><!-- 6插件 -->
<plugin interceptor="tk.mybatis.simple.plugin.PageInterceptor">
<property name="dialect" value="tk.mybatis.simple.plugin.MySqlDialect"/>
</plugin>
<!-- <plugin interceptor="tk.mybatis.simple.plugin.CameHumpInterceptor"/> -->
</plugins>
<environments default="development"><!-- 7环境集合属性对象(当mybatis与Spring整合之后,environments的配置将被废除) -->
<environment id="development"><!--使用jdbc事务管理,事务由mybatis控制-->
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED"><!-- 数据量少UNPOOLED 测试开发POOLED 运行JIDN;由mybatis控制连接池,与Spring整合之后由第三方连接池管理 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_name"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers><!-- 8映射器(两种配置语句:package name="xx"或者mapper resource= "xx") -->
<package name="mapper"/>
<!--<mapper resource="cn/wy/dao/mapper/UserMapper.xml"/>-->
</mappers>
</configuration>
代码_java_xml
测试用例_TestMybatis.java
public class MybatisTest{
@Test
public void findUserByIdTest() throws IOException{
String resource = "mybatisConfig.xml";//mybaits全局配置文件
InputStream inputStream = Resource.getRsourceAsStream(resource)//配置文件转stream
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//SqlSessionFactory读取stream
Sqlsession sqlSession = sqlSessionFactory.openSession();//SqlSessionFactory开启一个sqlSession
Pojo pojo = sqlSession.selectOne(statement(映射文件中statement的id[namespace+statementId]), parameter);
User user = sqlSession.selectOne("UserMapper.findUserById", 1-->parameterType="int");
System.out.println(user.toString());
sqlSession.close();
}
}
映射XML_UserMapper.xml
<select id="findUserById"-->namespace下该sql的id值 parameterType="int"-->输入参数的类型 resultType="cn.wy.domain.User"-->指定sql输出结果的映射的java对象>
SELECT * FROM user WHERE id = #{id}-->id指的是接收的输入参数名
</select>
区分selectOne & selectList
selectOne表示查询出一条记录进行映射。
如果使用selectOne可以实现,则使用selectList也可以实现,只是list中只有一个对象
selectList表示查询出一个列表(多条记录)进行映射。
如果使用selectList可以实现,这时候不能使用selectOne,会报错!
区分 #{} & ${}
- #{} 表示一个占位符
- 输入参数可以是po、hashMap、简单类型
- 输入参数若是简单类型,可以是value等任意str
- ${} 表示一个拼接符号,会引起sql注入, 不建议使用
- 输入参数可以是po、hashMap、简单类型
- 输入参数若是简单类型,只能写成value
完整版_UserMapper.xml
findUserById
<select id="findUserById" parameterType="int" resultType="cn.wy.domain.User">
SELECT * FROM user WHERE id = #{id}
</select>
@Test
public void findUserByIdTest() throws IOException{
String resource = "mybatisConfig.xml";
InputStream inputStream = Resource.getRsourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Sqlsession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("UserMapper.findUserById", 1);
System.out.println(user.toString());
sqlSession.close();
}
findUserByName
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.wy.domain.User">
SELECT * FROM user WHERE username LIKE $%{username}%
</select>
@Test
public void findUserByNameTest() throws IOException{
String resource = "mybatisConfig.xml";
InputStream inputStream = Resource.getRsourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Sqlsession sqlSession = sqlSessionFactory.openSession();
List<User> userList = sqlSession.selectList("UserMapper.findUserByName", "%marry%");
System.out.println(userList);
sqlSession.close();
}
insertUser
<insert id="insertUser" parameterType="cn.wy.domain.User">
INSERT INTO user(id, username, birthday, address) VALUE(#{id},#{username},#{birthday},#{address})
</insert>
@Test
public void insertUserTest() throws IOException{//由于主键自增,id可以不用写
String resource = "mybatisConfig.xml";
InputStream inputStream = Resource.getRsourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Sqlsession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUserName("aaa");
user.setSex("male");
user.setBirthay(new Date());
user.setAddress("aaaaaa");
sqlSession.insert("UserMapper.insertUser", user);
sqlSession.commit();
sqlSession.close();
}
自增主键的返回
<insert id="insertUser" parameterType="cn.wy.domain.User">
<selectKey keyProperty="id" order="After" resultType="java.lang.Integer">
<!-- 将插入数据的主键返回,返回到user对象中 -->
<!-- SELECT LAST_INSERT_ID()可以获取到insert进去的主键值(只适用于自增主键) -->
<!-- keyProperty:将查询到的主键值设置到parameterType指定的对象的哪个属性上 -->
<!-- order:相对于INSERT INTO语句来说他的执行顺序 -->
<!-- resultType:指定SELECT LAST_INSERT_ID()的返回值类型 -->
SELECT LAST_INSERT_ID()
</selectKey>
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
SELECT uuid()
</selectKey>
INSERT INTO user(id, username, birthday, address) VALUE(#{id},#{username},#{birthday},#{address})
</insert>
@Test
public void insertUserTest() throws IOException{//由于主键自增,id可以不用写
String resource = "mybatisConfig.xml";
InputStream inputStream = Resource.getRsourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Sqlsession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUserName("aaa");
user.setSex("male");
user.setBirthay(new Date());
user.setAddress("aaaaaa");
sqlSession.insert("UserMapper.insertUser", user);
sqlSession.commit();
System.out.println(user.getId());
sqlSession.close();
}
删除用户
<delete id="deleteUser" parameterType="java.lang.Integer">
DELETE FROM user WHERE id = #{id}
</delete>
@Test
public void deleteUserTest() throws IOException{//由于主键自增,id可以不用写
String resource = "mybatisConfig.xml";
InputStream inputStream = Resource.getRsourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Sqlsession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("UserMapper.deleteUser", 1);
sqlSession.commit();
sqlSession.close();
}
更新用户
<update id="updateUser" parameterType="cn.wy.domain.User">
UPDATE user SET username={username}, birthday={birthday}, address={address} WHERE id=#{id}
</update>
@Test
public void updateUserTest() throws IOException{//由于主键自增,id可以不用写
String resource = "mybatisConfig.xml";
InputStream inputStream = Resource.getRsourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Sqlsession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(1);
user.setUserName("bbb");
user.setSex("female");
user.setBirthay(new Date());
user.setAddress("aaaaaa");
sqlSession.update("UserMapper.insertUser", user);
sqlSession.commit();
sqlSession.close();
}
输入映射
- 通过parameterType指定输入参数的类型,类型可以是简单类型、hashMap、pojo的包装类型
传递pojo的包装对象
- mapper.xml(映射)
在UserMapper.xml中定义用户信息综合查询(查询条件复杂)
<select id="findUserList" parameterType="cn.wy.domain.pojo.UserQueryVo" resultType="cn.wy.domain.pojo.User">
SELECT * FROM user WHERE user_sex = 1 AND user_age >10
</select>
- mapper.java(接口)
public List<User> findUserList(UserQueryVo userQueryVo);
- 测试
@Test
public void testFindUserList() {
UserQueryVo userQueryVo = new UserQueryVo();
User user = new User();
user.setSex(1);
user.setAge(20);
userQueryVo.setUser(user);
List<User> userList = userMapper.findUserList(userQueryVo);
}
输出映射
resultType
- 使用resultType进行输出映射,只有查询出来的列名(表中的列名)和pojo对象中的属性名一致,才可以一一映射,否则将映射失败
- 若查询出来的列名(表中的列名)和pojo的属性名有部分不一致,会创建pojo对象,但是部分属性值为默认值
- 若查询出来的列名(表中的列名)和pojo的属性名完全不一致,则不会创建pojo对象
resultMap
- 如果查询出来的列名和pojo的属性名不一致,则需要通过写resultMap形成它们之间的映射
<resultMap id="userResultMap" type="cn.wy.domain.pojo.User">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_account" property="userAccount" jdbcType="VARCHAR"/>
</resultMap>
<select id="findUserByIdResultMap" resultMap="userResultMap">
select id,user_account from sys_user where id = #{id};
</select>
动态sql
什么是动态sql
- mybatis的核心是对sql语句进行灵活操作
- 通过表达式进行判断,对sql进行灵活拼接、组装
动态sql之<if test="">
<select id="findUserByIdResultMap" parameterType="UserQueryVo" resultMap="userResultMap">
select id,user_account from sys_user
<where>
<if test="user.id!=null and user.id!=''">
and user.id = #{user.id}
</if>
<if test="user.userAccount!=null and user.userAccount!=''">
and user.userAccount = #{user.userAccount}
</if>
</where>
</select>
sql片段
需求
- 动态sql的判断代码抽取出来,组成sql片段,其他的statement中就可以引用该sql片段,方便开发
定义sql片段
<!--
id是sql片段的唯一标识
经验:基于单表来定义sql片段,使得复用性高
sql片段中不要包含where标签
-->
<sql id="query_user_where">
<if test="user.id!=null and user.id!=''">
and user.id = #{user.id}
</if>
<if test="user.userAccount!=null and user.userAccount!=''">
and user.userAccount = #{user.userAccount}
</if>
</sql>
引用sql片段
在userMapper.xml中定义的statement中引用sql片段
<select id="findUserByIdResultMap" parameterType="UserQueryVo" resultMap="userResultMap">
select id,user_account from sys_user
<where>
<!-- 引用sql片段的id值 -->
<include refid="query_user_where"></include>
<!-- 在这里继续引用其他的sql标签 -->
</where>
</select>
foreach
- 传递数组或List的时候,mybatis使用foreach解析
需求
- 需要增加多个id输入查询
- 比如
SELECT * FROM user WHERE id in (1,5,10)
SELECT * FROM user WHERE id = 1 OR id = 5 OR id = 10
在parameterType中添加List<Integer> ids传入多个id
<!--
使用foreach遍历传入id
collection:指 输入对象中的集合属性 --- 传进来的集合名字ids
item:每次遍历生成对象名字,随便叫什么 --- 那就叫id
open:开始遍历时所要拼接的串
close:结束遍历时所要拼接的串
separator:遍历的两个对象中间需要拼接的串
查询语句:SELECT * FROM user WHERE sex = "male" AND (id = 1 OR id = 5 OR id = 10)
用来拼接的串:AND (id = 1 OR id = 5 OR id = 10)
-->
<if test="ids!=null">
<foreach collection="ids" item="user_id" open="AND (" close=")" separator="OR">
<!-- 每次遍历要拼接的串 -->
id = #{user_id}
</foreach>
</if>
区别mybatis和hibernate
看看hibernate
- 是一个标准的ORM框架,入门难,自动生成SQL
- 对SQL语句优化、修改比较困难
- 适用于需求变化不多的小型项目,比如:ORM、ERP
看看mybaits
- 非标准ORM的框架,入门简单,程序员自己写SQL语句
- 对SQL语句优化、修改容易
- 虽然程序员自己写SQL,但是mybatis也可以实现输入输出映射
- 适用于需求变化较多的项目,比如:互联网项目
高级映射
数据模型分析思路
- 分模块熟悉各表记录的数据内容
- 按照非空、外键来看重要字段
- 理解表与表的关系(外键关系、业务关系 )
一对一查询
SQL语句 & PO
一对一小结
- mybatis使用resultMap的 association 对级联查询的单条记录映射到Order类的user属性中
- resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对象的属性即可完成映射(没有特殊要求的查询)
- resultMap:需要单独定义resultMap,实现麻烦,但是可以实现查询复杂的结果,将其关联映射pojo的属性中
- resultMap可以实现延迟加载
- resultType则不可以延迟加在
一对多查询
SQL语句 & PO
一对多小结
- mybatis使用resultMap的 collection 对关联查询的多条记录映射到一个list集合属性中
- 若使用resultType则需要自己去实现(将订单明细映射到orders中的orderDetailList中,使用双重循环遍历,去掉重复记录)
多对多查询
SQL语句 & PO
多对多小结
- 一对多是多对多的特例
- 使用resultMap是针对那些查询结果映射有特殊要求的功能
总结
- resultType
- 作用:将查询结果按照【sql列名】和【pojo属性名】一致性映射到pojo中
- resultMap
- 使用association和collection完成一对一和一对多的高级映射( 对结果有特殊的映射要求 )
- association作用
- 将关联查询信息映射到一个pojo对象中
- collection作用
- 将关联查询信息映射到一个list集合中
延迟加载
什么是延迟加载
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
查询缓存
什么是查询缓存
- 一级缓存
- 一级缓存 是SqlSession级别的缓存
- 一级缓存 在操作数据库时需要构造SqlSession对象,SqlSession中有一个内存数据结构HashMap用与存储缓存数据
- 一级缓存 的不同SqlSession之间的缓存数据区域(HashMap)互不影响
- 二级缓存
- 二级缓存 是mapper级别的缓存
- 多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存(二级缓存跨SqlSession)
一级缓存
mybatis默认支持一级缓存,不需要配置
- 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息
若没有,则去数据库中查询该用户的信息
如果SqlSession去执行commit操作(即CRUD),清空SqlSession中的一级缓存(这样做的目的是使缓存中的数据永远是最新的,避免赃读)
第二次发起查询用户id为1的用户信息,先去找缓存,发现有该数据,直接从缓存中读取该信息
如果是两次service调用查询相同的用户信息,是不走一级缓存的,因为session方法结束,SqlSession就关闭,一级缓存就会被清空。
二级缓存
首先要开启mybatis的二级缓存
- mybaits的 二级缓存是mapper级别 的,除了在 SqlMapConfif.xml中设置二级缓存的总开关 , 还要在具体的mapper.xml中开启二级缓存开关 ,默认true
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
<mapper namespace="cn.wy.dao.UserMapper">
<cache />
</mapper>
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.getMapper(UserMapper.class);
System.out.println(user);
sqlSession.close();//只有将它关闭,才能将sqlSession中的数据写到二级缓存中
禁止使用二级缓存useCache
在statement中设置useCache=false可以禁止当前select语句的二级缓存,即每次查询都会发出sql去查询。 默认情况是true
总结:针对每次查询都需要最新的数据的sql语句,要设置成useCache=false,禁止二级缓存。
<select id="findUserByIdResultMap" resultMap="userResultMap" useCache="false">
select id,user_account from sys_user where id = #{id};
</select>
刷新缓存(清空缓存)
在mapper的同一个namespace中,如果有其他的insert、update、delete操作数据库后需要刷新缓存。如果不刷新,会赃读。
设置statement中flushCache=false则不会刷新。默认true
手动修改修改数据库表中的数据,会赃读。
总结:一般执行完commit操作都需要刷新缓存,从而避免赃读。
<select id="findUserByIdResultMap" resultMap="userResultMap" useCache="false" flushCache="false">
select id,user_account from sys_user where id = #{id};
</select>
一级/二级缓存的区别:
-
- 二级缓存 【更大】
- 多个SqlSession可以共享一个UserMapper的二级缓存区域 【按照namespace划分二级缓存空间】 ,其他的mapper也有各自的二级缓存区域
- 若某个 SqlSession去执行同一个mapper下的commit_sql语句 ,则会 清空该mapper的二级缓存
Mybatis整合ehcache
ehcache是分布式缓存框架
分布式缓存
- 若不使用分布式缓存,缓存的数据在各个服务器单独存储。
- 因此需要使用分布式缓存框架(redis,memcached,ehcache)对缓存数据进行集中管理。(缓存集群)
- mybatis无法实现分布式缓存,因此要用其他缓存框架整合
整合方法
- mybatis提供cache接口,如果要实现自己的缓存逻辑,则只要实现cache接口开发即可
- 接口类
- 实现类
mybatis和ehcache的整合
需要的jar包
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
</dependency>
开启mapper的namespace下的二级缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache与ehcache整合:配置type为ehcache实现cache接口的类型
<mapper namespace="cn.wy.dao.UserMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
二级缓存的应用场景
对于访问较多,并且对查询结果实时性要求不高的查询请求,可以使用二级缓存。(降低数据库访问量,提高访问速度)
实现方法:通过设置刷新时间间隔(flushinterval设置为30分钟、60分钟、24小时),由mybatis每隔一段时间自动清空缓存,根据需求而定。
局限性
mybatis二级缓存 对细粒度的数据级别的缓存实现不好 。比如缓存了1W个商品信息,当一个商品变化之后,全部的缓存就会被清空。
需要在业务层根据需求对数据有针对性缓存 。
Spring和mybatis整合
大致方向
- Spring通过单例方式管理SqlSessionFactory
- Spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession(Spring和mybatis整合后自动完成)
- 持久层的mapper都需要Spring进行管理