mybatis学习总结
一, day01学习小结
1.1 三层架构和ssm框架
1.什么是框架?使用框架有什么好处?2.三层架构分别指的是什么?
框架作为一个半成品软件,它是我们软件开发过程中的一套解决方案,不同框架解决不同的问题.
好处:框架封装了很多的细节,使开发者可以用极简的方式实现功能,可以大大提高开发效率以及提高软件开发质量.
表现层(Web层):用于向用户展示相关数据信息(如SpringMVC框架);
业务层(Service层):用于处理业务核心逻辑;
持久层(Dao层):用于与数据库进行交互(如Mybatis,Struts框架);
备注:准确来说Spring不属于任何一层.
1.2 Mybatis概述
-
基本信息
- MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录.
- 它使用ORM(Object Relational Mapping对象关系映射)思想实现了结果集的封装.简单来说,就是数据库表的一条记录对应一个对象.(数据库中的表和列 <--------对应---------> 实体类和成员变量)
-
优点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。SQL和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态SQL。
-
功能框架
我们把Mybatis的功能架构分为三层:
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
-
框架架构
- 加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
- SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
- SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
- 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
1.3 Mybatis入门
-
环境搭建
- 创建maven工程,导入相应的jar包坐标(MySQL驱动,Mybatis,Junit,Log4j);
- 创建对应数据库的实体类和相应的Dao接口(如User类和IUserDao接口);
- 创建Mybatis主配置文件SqlMapConfig.xml;
- 创建相应的SQL映射配置文件.
- 注意事项: Mybatis映射文件位置必须和dao接口的包结构一致;映射文件的mapper标签namespace属性值必须是dao的全限定类名;映射文件的crud操作配置中,id取值必须是dao接口的方法名.
-
CRUD入门
public class MybatisTest { public static void main(String[] args) throws Exception{ // 1.使用ibatis的Resources.getResourceAsStream()方法来读取主配置文件 // SqlMapConfig.xml文件,获得InputStream流 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); // 2.利用SqlSessionFactoryBuilder构建者的build()方法来获取SqlSessionFactory工厂 // 类对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = builder.build(is); // 3.利用SqlSessionFactory工厂的openSession()方法获得SqlSession对 // 象,openSession()方法传入true值可以开启事务的自动提交,但是实际操作不推荐 SqlSession sqlSession = sqlSessionFactory.openSession(); // 4.利用SqlSession.getMapper(dao接口的字节码对象)获得此dao接口的代理对象 IUserDao mapper = sqlSession.getMapper(IUserDao.class); // 5.调用代理对象的相应方法进行数据库的crud操作 List<User> users = mapper.findAll(); for (User user : users) { System.out.println(user); } // 6.关闭SqlSession,InputStream资源 sqlSession.close(); is.close(); } }
-
#{}与${}的区别
-
mybatis中的设计模式
- Builder模式,例如SqlSessionFactoryBuilder、Environment;
- 工厂方法模式,例如SqlSessionFactory、TransactionFactory、TransactionFactory、LogFactory、ObjectFactory、ReflectorFactory;
- 单例模式,例如ErrorContext和LogFactory;
- 代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
- 组合模式,例如SqlNode和各个子类ChooseSqlNode等;
- 模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
- 适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
- 装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现;
- 迭代器模式,例如迭代器模式PropertyTokenizer;
二, day02学习小结
2.1 mybatis的CRUD
-
IUserDao.xml映射文件配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hooware.dao.IUserDao">
<!-- 根据id删除用户 -->
<delete id="deleteUser" parameterType="INT">
delete from user where id = #{uid}
</delete>
<!-- 根据id更新用户 -->
<update id="updateUser" parameterType="com.hooware.domain.User">
update user set username = #{username}, sex = #{sex}, birthday = #{birthday} where id = #{id}
</update>
<!-- 根据id查询用户 -->
<select id="findById" parameterType="int" resultType="com.hooware.domain.User">
select * from user where id = #{uid}
</select>
<!-- 插入用户数据并查询插入用户的id -->
<insert id="saveUser" parameterType="com.hooware.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user(username, birthday, sex, address) VALUES(#{username},
#{birthday}, #{sex}, #{address})
</insert>
<!-- 模糊查询,QueryVo用户封装查询的多个参数 -->
<select id="findByQueryVo" parameterType="com.hooware.domain.QueryVo" resultType="com.hooware.domain.User">
select * from USER where username like #{user.username};
</select>
</mapper>
2.2 实体类属性和数据库字段名不一致问题
为什么只有userName能拿到数据,是因为MySQL在window环境下是不区分大小写的,可以封装,linux下区分大小写,则无法进行封装.
- 方案一:在映射文件中给字段起别名,能够保证属性与字段对应一致就可以,
- 方案二:创建ResultMap配置属性与字段对应,如下:
- SQL语句给字段起别名:(执行效率高)
优点:SQL执行效率高
缺点:每个SQL语句都要改
- 配置ResultMap映射:(开发效率高)
优点: 只配置一次,其他地方引用即可,开发效率高
缺点: 执行效率稍低
2.3 properties标签的使用及细节
- 可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息.
- resource属性: 用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
- url属性: 是要求按照Url的写法来写地址. (URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置. URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的)
2.4 typeAliases标签和package标签
在映射文件中是有默认的别名的,比如int,但是自己定义的User是不能使用别名的,要想使用别名,可以在主配置文件中使用typeAliases配置自己的别名.
-
SqlMapConfig.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="jdbcConfig.properties" />
<!--延迟加载设置-->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
<!--<setting name="cacheEnabled" value="true"/>-->
</settings>
<typeAliases>
<package name="com.hooware.domain" />
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<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>
<package name="com.hooware.dao" />
</mappers>
</configuration>
-
SQL映射文件配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hooware.dao.IUserDao">
<!--<!– 开启二级缓存支持 –>-->
<!--<cache />-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="accounts" ofType="account" column="id" fetchType="lazy" select="com.hooware.dao.IAccountDao.findByUid">
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
SELECT * from user
<!-- SELECT u.*, a.ID as aid, a.uid, a.money from user u LEFT JOIN account a on u.id = a.uid -->
</select>
<select id="findById" resultMap="userAccountMap" parameterType="int">
SELECT * from user where id = #{value}
</select>
</mapper>
三, day03学习小结
3.1 mybatis连接池的分类
- type=”POOLED” :MyBatis会创建PooledDataSource实例.
- type=”UNPOOLED” :MyBatis会创建UnpooledDataSource实例.
- type=”JNDI” :MyBatis会从JNDI服务上查找DataSource实例,然后返回使用.
3.2 mybatis中使用poolead配置连接的原理分析
- 为什么要使用带连接池的数据源呢,最根本的原因还是因为每次创建连接开销比较大,频繁的创建和关闭数据库连接将会严重的影响性能。因此,常用的做法是维护一个数据库连接池,每次使用完之后并不是直接关闭数据库连接,再后面如果需要创建数据库连接的时候直接拿之前释放的数据库连接使用,避免频繁创建和关闭数据库连接造成的开销。
- 在mybatis中,定义了一个数据库连接池状态的类PoolState,在这个类里,除维护了数据源实例,还维护着数据库连接。数据库连接被分成了两种状态类型并存放在两个列表中:idleConnections和activeConnections。
- idleConnections:
- 空闲(idle)状态PooledConnection对象被放置到此集合中,表示当前闲置的没有被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从此集合中取PooledConnection对象。当用完一个java.sql.Connection对象时,MyBatis会将其包裹成PooledConnection对象放到此集合中。
- activeConnections:
- 活动(active)状态的PooledConnection对象被放置到名为activeConnections的ArrayList中,表示当前正在被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从idleConnections集合中取PooledConnection对象,如果没有,则看此集合是否已满,如果未满,PooledDataSource会创建出一个PooledConnection,添加到此集合中,并返回.
-
从PooledDataSource类获取连接的过程
- 查看如下源码可以得出如下结论:
- 先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。
- 查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步;
- 看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步;
- 线程等待,循环2步。
/* 调用了popConnection()方法,然后返回其代理对象 */
@Override
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();
}
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {
synchronized (state) {
if (!state.idleConnections.isEmpty()) {
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back");
}
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// Must wait
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
3.3 mybatis中的事务
- 事务的概念
事务:一个逻辑单元中的一组操作,要么全部成功,要么全部失败.
- 事务的4个特性
(ACID)原子性、一致性、隔离性和持久性
- 不考虑事务的隔离性可能产生的3个问题:
- 脏读:一个事务,读到另一个事务未提交的数据. (read uncommitted可能出现脏读)
- 不可重复读:一个事务读到另一个事务已经提交的update的数据,导致一个事务中多次查询结果不一致!
- 虚读(幻读):一个事务读到另一个事务已经提交的insert数据,导致一个事务中多次查询的记录条数不一致!
MySQL数据库的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
3.4 mybatis中的动态SQL语句(if,where,foreach标签)
- where标签: 可以自动处理第一个and, 用了,SQL不用加"where 1 = 1".
- if标签: test属性中写的是对象的属性名,如果是包装类的对象要使用OGNL表达式的写法.
- foreach标签: 用于遍历集合. 有如下属性:
- collection:代表要遍历的集合元素,注意编写时不要写#{}
- open:代表语句的开始部分
- close:代表结束部分
- item:代表遍历集合的每个元素,生成的变量名
- sperator:代表分隔符
<!-- if,where,foreach标签使用示例 -->
<select id="findByIds" resultType="user" parameterType="queryVo">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open=" and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
3.5 mybatis多表关系分析
- mybatis多表关系只有3种:
- 一对一.(多对一在mybatis中也是一对一的关系)
- 一对多.
- 多对多.
-
账户和用户的一对一关系
- 在Account实体类中加入一个user引用.
- 在IAccountDao.xml映射文件的ResultMap中添加标签,加入user类的映射关系.
<resultMap id="accountUserMap" type="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> SELECT u.*, a.id as aid, a.uid, a.money from account a left join user u on a.uid = u.id </select>
-
用户和账户的一对多关系
- 在User实体类中加入一个List accounts集合引用.
- 在IUserDao.xml.xml映射文件的ResultMap中添加标签,加入user类的映射关系.
<resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT u.*, a.ID as aid, a.uid, a.money from user u LEFT JOIN account a on u.id = a.uid </select>
-
用户和角色的多对多关系
- 不管是用户和角色的关系还是角色和用户的关系,本质上都是一对多的关系,只不过SQL语句要借助中间表来处理.具体操作参照如上一对多的步骤.
四, day04学习小结
4.1 延迟加载和立即加载
- 什么是延迟加载?
在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载).- 什么是立即加载?
不管用不用,只要一调用方法,马上发起查询.一对多,多对多:通常情况下我们都是采用延迟加载.
一对一:通常情况下我们都是采用立即加载.
4.2 延迟加载操作
-
一对一延迟加载
- 在SqlMapConfig.xml配置标签,开启延迟加载.
<!--延迟加载设置--> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>--> </settings>
注意: 指定对象的哪个方法触发一次延迟加载在mybatis中,默认情况下只要调用了equals,clone,hashCode,toString这几个方法,都会对对象进行完全加载. 因为这里我需要调用toString方法,查看返回的数据,又不想触发完全加载,所以配置了一个hashCode<setting name="lazyLoadTriggerMethods" value="hashCode"/>
- 对应SQL映射文件配置.
<resultMap id="accountUserMap" type="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user" fetchType="eager" select="com.hooware.dao.IUserDao.findById"> </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> SELECT * FROM account <!-- SELECT u.*, a.id as aid, a.uid, a.money from account a left join user u on a.uid = u.id --> </select>
注意:
select : 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
fetchType: eager立即加载, lazy延迟加载,可以覆盖全局设置的延迟加载策略.
javaType: 返回值为User类型.
-
一对多延迟加载
- 在SqlMapConfig.xml配置标签,开启延迟加载.
<!--延迟加载设置--> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>--> </settings>
注意: 指定对象的哪个方法触发一次延迟加载在mybatis中,默认情况下只要调用了equals,clone,hashCode,toString这几个方法,都会对对象进行完全加载. 因为这里我需要调用toString方法,查看返回的数据,又不想触发完全加载,所以配置了一个hashCode<setting name="lazyLoadTriggerMethods" value="hashCode"/>
- 对应SQL映射文件配置.
<resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="password" column="password"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <collection property="accounts" ofType="account" column="id" fetchType="lazy" select="com.hooware.dao.IAccountDao.findByUid"> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * from user <!-- SELECT u.*, a.ID as aid, a.uid, a.money from user u LEFT JOIN account a on u.id = a.uid --> </select>
注意:
select : 用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column : 用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列
fetchType: eager立即加载, lazy延迟加载,可以覆盖全局设置的延迟加载策略.
ofType: List泛型类型为account类型.
4.3 一级缓存和二级缓存
- 什么是缓存
存在于内存中的临时数据。
- 为什么使用缓存
减少和数据库的交互次数,提高执行效率。
什么样的数据能使用缓存,什么样的数据不能使用
适用于缓存:
经常查询并且不经常改变的。
数据的正确与否对最终结果影响不大的。
不适用于缓存:
经常改变的数据
数据的正确与否对最终结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价。
SqlSession一级缓存:
- 它指的是Mybatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中 查询是否有,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了。
- 当调用SqlSession的增删改,commit(),close(),clearCache()等方法时,就会清空一级缓存.
- 一旦当前sqlsession 执行update,delete,insert操作,把当前sqlsession缓存的所有数据,都清空,而不是清空修改的那一条数据!
- 在一个sqlSession范围内, 缓存的数据有可能和数据库不一致的情况发生, 当一级缓存后,另外的sqlsession或者直接在数据库修改后,会导致缓存和数据库两边数据不一致!
SqlSessionFactory二级缓存
- 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存开启步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置,默认开启)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)<settings> <setting name="cacheEnabled" value="true"/> </settings>
第三步:让当前的操作支持二级缓存(在select标签中配置useCache=“true”)<!-- 开启二级缓存支持 --> <cache />
- 二级缓存缓存的是数据而不是对象,从缓存中获取到对象重新组装成对象返回所以当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable接口,这种就可以使用序列化方式来保存对象.
4.4 注解开发
注意: 对于dao 中的某个方法而言,不能注解和xml配置文件同时使用!!!
-
一对一注解开发
public interface IAccountDao {
/**
* 查询所有账户
* @return
*/
@Select(value="select * from account")
@Results(id = "accountMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "uid", column = "uid"),
@Result(property = "money", column = "money"),
@Result(property = "user", column = "uid", one = @One(select = "com.hooware.dao.IUserDao.findById", fetchType = FetchType.EAGER))
})
List<Account> findAll();
/**
* 根据用户ID查询账户
* @param uid
* @return
*/
@Select("select * from account where uid = #{uid}")
@ResultMap("accountMap")
List<Account> findByUid(Integer uid);
}
-
一对多注解开发
//@CacheNamespace(blocking = true) // 开启二级缓存
public interface IUserDao {
/**
* 查询所有用户信息
* @return
*/
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "birthday", column = "birthday"),
@Result(property = "address", column = "address"),
@Result(property = "sex", column = "sex"),
@Result(property = "accounts", column = "id", many = @Many(select = "com.hooware.dao.IAccountDao.findByUid", fetchType = FetchType.LAZY))
})
List<User> findAll();
/**
* 根据id查询用户
* @param id
* @return
*/
@Select("select * from user where id = #{id}")
@ResultMap("userMap")
User findById(int id);
}