2j Myatis.txt
UP 返回
1.基础
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。(对jdbc的封装框架有哪些:Hibernate,dbutils,jdbcTemplate[spring],mybatis)
▶原理:Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
2.MyBatis的框架核心
mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的 信息。
mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
通过SqlSessionFactory,可以创建SqlSession即会话。Mybatis是通过SqlSession来操作数据库的。
SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括HashMap集合对象、POJO对象类型。
3.项目配置
3.1 下载:
mybaits的代码由github.com管理,下载地址:https://github.com/mybatis/mybatis-3/releases
3.2 创建数据库表(本地为:MySQL/mybatis_test数据库)
建库语句:F:\BaiduNetdiskDownload\2019年4月黑马程序员教程\01-黑马IDEA版本Java基础+就业课程\4.框架\04.MyBatis\资料\sql
3.3 创建一个简单项目(java→java EE→java hello world),jar导入:
D:\EnvironmentExtends\otherdownload下的jar,以及其中lib下所有的jar导入
mysql-connector-java-5.1.7-bin.jar 驱动,位置:F:\BaiduNetdiskDownload\2019年4月黑马程序员教程\01-黑马IDEA版本Java基础+就业课程\4.框架\04.MyBatis\资料
3.4 添加log4j.properties
Mybatis使用的日志包是log4j的,所以需要添加log4j.properties。
在classpath下(即src目录下)创建log4j.properties如下:【文件内容可以从mybatis-3.2.7.pdf中拷贝】
# Global logging configuration
log4j.rootLogger=DEBUG, stdout #只输出到控制台,文件的话这后面还要加一个文件。▶日志级别在开发阶段设置成DEBUG,在生产阶段设置成INFO或者ERROR。
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
4.代码编写
4.1 创建PO(model)类,根据需求创建;
■com.dm.model.User.class
public class User implements Serializable {
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
}
4.2 创建全局配置文件SqlMapConfig.xml;
★src下创建SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- ▶这个dtd文件是对xml下的结点进行限制,防止乱写结点 -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- ▶配置mybatis的环境信息。里面可以配置多个环境(数据源) -->
<environments default="development">
<environment id="development">
<!-- ▶配置JDBC事务控制,由mybatis进行管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- ▶配置数据源,采用dbcp连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123654"/>
</dataSource>
</environment>
</environments>
<!--▶告诉mybatis加载映射文件-->
<mappers>
<mapper resource="com/dm/sqlmap/User.xml"></mapper> ▶和对应的model对应起来
</mappers>
</configuration>
4.3 编写映射文件;
★com.dm.sqlmap/User.xml(和model包中POJO对应)
<?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">
<!--
▶namespace:命名空间,它的作用就是对SQL进行分类化管理,可以理解为SQL隔离。可以根据模块去划分就行了
注意:使用mapper代理开发时,namespace有特殊且重要的作用
-->
<mapper namespace="test">
<!--▶▶▶▶
id:statement的id,要求在命名空间内唯一
parameterType:入参的java类型
resultType:查询出的单条结果集对应的java类型
#{}: 表示一个sql语句中的占位符?
#{id}:表示该占位符待接收参数的名称为id。注意:如果参数为简单类型时,#{}里面的参数名称可以是任意定义
-->
<select id="findUserById" parameterType="int" resultType="com.dm.model.User"> ▶执行的语句
SELECT * FROM USER WHERE id = #{id}
</select>
</mapper>
4.4 加载映射文件,在SqlMapConfig.xml中进行加载;
见4.2最后几句代码
4.5 编写测试程序,即编写Java代码,连接并操作数据库。
■测试类代码
public class Test01 {
@Test
public void test1() throws IOException {
//读取配置文件;
InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
//通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂。
SqlSessionFactory sessionFactory=new SqlSessionFactoryBuilder().build(is);
//通过SqlSessionFactory创建SqlSession。
SqlSession session=sessionFactory.openSession();
//调用SqlSession的操作数据库方法。
User user=session.selectOne("findUserById",10);
System.out.println(user);
//关闭SqlSession。
session.commit();
}
}
5.一些常用写法
5.1 模糊查询
■映射文件中SQL的写法:
<!--
[${}]:表示拼接SQL字符串
[${value}]:表示要拼接的是简单类型参数。
注意:
1、如果参数为简单类型时,${}里面的参数名称必须为value◀◀◀◀◀
2、${}会引起SQL注入,一般情况下不推荐使用。但是有些场景必须使用${},比如order by ${colname}
-->
<select id="findUserByName" parameterType="String" resultType="com.dm.model.User">
SELECT * FROM USER WHERE username LIKE '%${value}%' ▶简单类固定写为value
</select>
■测试类中的写法:
List<User> users=session.selectList("findUserByName","张");
5.2 其他语句
■SQL映射文件
<insert id="insertUser" parameterType="com.dm.model.User">
INSERT INTO USER (username,sex,birthday,address) VALUE (#{username},#{sex},#{birthday},#{address})
</insert>
<delete id="deleteUser" parameterType="int">
DELETE FROM USER WHERE id = #{id}
</delete>
<update id="updateUser" parameterType="com.dm.model.User">
UPDATE USER SET username=#{username},sex=#{sex},address=#{address} WHERE id = #{id}
</update>
■测试类
session.insert("insertUser",user);
session.commit();//不会自动提交,需要自己写
session.insert("deleteUser",27);
User user=new User("修改","0",new Date(),"田径");
user.setId(28);
session.update("updateUser",user);
5.3 主键返回之MySQL自增主键
思路:
MySQL自增主键,是指在insert之前MySQL会自动生成一个自增的主键。
我们可以通过MySQL的函数获取到刚插入的自增主键:LAST_INSERT_ID()
这个函数是在insert语句之后去调用。
■SQL写法:
<insert id="insertUser" parameterType="com.dm.model.User">
<!--
[selectKey标签]:通过select查询来生成主键
[keyProperty]:指定存放生成主键的属性
[resultType]:生成主键所对应的Java类型
[order]:指定该查询主键SQL语句的执行顺序,相对于insert语句
[last_insert_id]:MySQL的函数,要配合insert语句一起使用 -->
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
<!-- 如果主键的值是通过MySQL自增机制生成的,那么我们此处不需要再显示的给ID赋值 -->
INSERT INTO USER (username,sex,birthday,address) VALUE (#{username},#{sex},#{birthday},#{address})
</insert>
5.4 主键返回之MySQL自增UUID
<insert id="insertUser" parameterType="com.gyf.domain.User">
<selectKey keyProperty="id" resultType="String" order="BEFORE">
SELECT UUID()
</selectKey>
INSERT INTO USER (username,sex,birthday,address)
VALUES(#{username},#{sex},#{birthday},#{address})
</insert>
5.5 ORCLE主键
SELECT user_seq.nextval() FROM dual
5.6 小结
parameterType和resultType
parameterType指定输入参数的java类型,可以填写别名或Java类的全限定名。
resultType指定输出结果的java类型,可以填写别名或Java类的全限定名。
#{}和${}
#{}:相当于预处理中的占位符?。
#{}里面的参数表示接收java输入参数的名称。
#{}可以接受HashMap、POJO类型的参数。
当接受简单类型的参数时,#{}里面可以是value,也可以是其他。
▶#{}可以防止SQL注入。
${}:相当于拼接SQL串,对传入的值不做任何解释的原样输出。
▶${}会引起SQL注入,所以要谨慎使用。
${}可以接受HashMap、POJO类型的参数。
当接受简单类型的参数时,${}里面只能是value。
selectOne和selectList
selectOne:只能查询0或1条记录,大于1条记录的话,会报错:
selectList:可以查询0或N条记录
6.MyBatis的Dao编写
6.1 手动编写【一般不用,有更多好方式】
■UserDao接口
public interface UserDao {
public void save(User user);
public User findUserById(int id);
}
■UserDaoImpl
public class UserDaoImpl implements UserDao {
private SqlSessionFactory ssf;
public void setSsf(SqlSessionFactory ssf) {
this.ssf = ssf;
}
public UserDaoImpl(SqlSessionFactory ssf) {
this.ssf = ssf;
}
@Override
public void save(User user) {
SqlSession session=ssf.openSession();
session.insert("insertUser",user);
session.commit();
session.close();
}
@Override
public User findUserById(int id) {
SqlSession session=ssf.openSession();
User user=session.selectOne("findUserById",id);
session.close();
return user;
}
}
■测试类
public class TestDao {
SqlSessionFactory sessionFactory;
@Before
public void before() throws IOException {
//读取配置文件;
InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
//通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂。
sessionFactory=new SqlSessionFactoryBuilder().build(is);
}
@Test
public void test4() {
UserDao userDao=new UserDaoImpl(sessionFactory);
User user=userDao.findUserById(28);
System.out.println(user);
}
}
6.2 MyBatis的Dao编写【mapper代理方式实现】
Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。Mybatis会自动的为mapper接口生成动态代理实现类。不过要实现mapper代理的开发方式,需要遵循一些开发规范:
mapper接口的全限定名要和mapper映射文件的namespace的值相同。
mapper接口的方法名称要和mapper映射文件中的statement的id相同。
mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致。
mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致
代码编写:新建包com.dm.mapper
■创建接口UserMapper
public interface UserMapper {
public int save(User user); ▶接口中的方法只允许有一个参数,且与xml中参数类型对应
public User findUserById(int id);
}
■创建UserMapper.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.dm.mapper.UserMapper"> ▶
<insert id="save" parameterType="com.dm.model.User"> ▶
INSERT INTO USER (username,sex,birthday,address)
VALUE (#{username},#{sex},#{birthday},#{address})
</insert>
<select id="findUserById" parameterType="int" resultType="com.dm.model.User"> ▶
SELECT * FROM USER WHERE id = #{id}
</select>
</mapper>
★修改加载的映射文件
<mappers>
<!--<mapper resource="com/dm/sqlmap/User.xml"></mapper>--> ▶这是之前的
<mapper resource="com/dm/mapper/UserMapper.xml"></mapper>
</mappers>
■测试:
public void test2(){
SqlSession session=sessionFactory.openSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
System.out.println(userMapper.findUserById(28));
userMapper.save(new User("test1","1",new Date(),""));
session.commit();
session.close();
}
7.配置文件SqlMapConfig.xml的一些参数
7.1 类似于spring,mybatis也可以将数据库信息单独放入一个文件中
★src下新建db.properties
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&characterEncoding=utf8
db.username=root
password=123654
★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="db.properties"/> ▶指定properties文件
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driverClass}"/> ▶引用数据
<property name="url" value="${url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/dm/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
7.2 setting配置(了解),里面有一系列的参数和值可供使用
<settings>
<setting name="" value=""/>
</settings>
7.3 别名 typeAliases
别名是使用是为了在映射文件中,更方便的去指定参数和结果集的类型,不再用写很长的一段全限定名。
▶mybatis支持的别名◀ (参mybatis文档)
别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
▶自定义别名◀
单个类的别名配置:
<typeAliases>
<typeAlias type="com.dm.model.User" alias="user"/> ▶这样在SQL的映射文件中就可以用user来作为类型
</typeAliases>
指定包名:
<typeAliases>
<!--▶指定包名,则包下的类的别名就是类名(第一个字母小写)-->
<package name="com.dm.model"></package>
</typeAliases>
7.4 mappers 加载映射文件的多种写法
<mapper resource=’’/> 使用相对于类路径的资源,如:<mapper resource="sqlmap/User.xml" />
<mapper url=’’/> 【不用】 使用完全限定路径,如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
<mapper class=’’/> 使用mapper接口的全限定名,如:<mapper class="cn.gyf.mybatis.mapper.UserMapper"/> ▶一定要有个同名的映射文件与之对应,如果没有的话,需要在类上加注解,这样就可以不用映射文件:
public interface UserMapper {
@Insert("INSERT INTO USER (username,sex,birthday,address) VALUE (#{username},#{sex},#{birthday},#{address})") ▶注解
public int save(User user);
@Select("SELECT * FROM USER WHERE id = #{id}")
public User findUserById(int id);
}
<package name=’’/>(推荐)注册指定包下的所有映射文件,如:<package name="cn.gyf.mybatis.mapper"/> ▶注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;
8.映射文件的参数
8.1 输入映射 ParameterType
指定输入参数的java类型,可以使用别名或者类的全限定名。它可以接收简单类型,POJO对象、HashMap。
参数是Map直接写hashMap就可以了:
<select id="findByMap" parameterType="hashMap" resultType="string">
SELECT number FROM orders a,user b WHERE a.user_id = b.id and b.id=#{id}
</select>
8.2 输出映射 resultType/resultMap
resultType
使用resultType进行结果映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功。如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象;如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象。
输出简单类型,当输出结果只有一列时,可以使用ResultType指定简单类型作为输出结果类型。(比如select count(*)的返回类型可以设为int)
resultMap
如果查询出来的列名和属性名不一致,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
<!--自己定义一个map-->
<resultMap id="userResultMap" type="user">
<id property="id" column="id_"></id> ▶id列就用id,其他列用result
<result property="username" column="username_"></result>
<result property="sex" column="sex_"></result>
</resultMap>
<!--结果就用这个map,会自动映射回模型-->
<select id="findUserTest" parameterType="int" resultMap="userResultMap"> ▶使用定义的map作为返回类型
/*假设查找出的结果的列名与模型中变量不同*/
SELECT id id_,username username_,sex sex_ FROM User WHERE id=#{?}
</select>
8.3 动态SQL
if和where
If标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。▶注意:用if进行判断是否为空时,不仅要判断null,也要判断空字符串‘’;
Where标签:会去掉条件中的第一个and符号。
<!--▶if标签可以不断嵌套来达到多重条件-->
<select id="findUserList" parameterType="map" resultType="user">
SELECT * FROM user
<where>
<if test="sex != null and sex != ''">
sex=#{sex}
</if>
<if test="name != null and name != ''">
AND username LIKE '%${name}%' ▶测试发现即使前一个条件没有只有这一个条件,语句前的AND会被自动去掉
</if>
</where>
</select>
SQL片段 提高SQL可重用性
<sql id="select_user_where"> ▶片段
<if test="sex != null and sex != ''">
sex=#{sex}
</if>
<if test="name != null and name != ''">
AND username LIKE '%${name}%'
</if>
</sql>
<select id="findUserList" parameterType="map" resultType="user">
SELECT * FROM user
<where>
<include refid="select_user_where"></include> ▶加入片段
</where>
</select>
foreach 遍历
<select id="findUserByIds" parameterType="userQueryDao" resultType="user"> ▶如果是传入一个查询VO(定义一个UserQueryDao,其中含有变量List<Integer>)
SELECT * FROM user
<where>
<if test="ids != null and ids.size > 0">
/*
collection:集合,写集合的属性 ▶注意编辑器中的/**/注释也会被打印在SQL语句中,但是其实并不影响
item:遍历接收的变量
open:遍历开始
close:遍历结束
separator:拼接字符
*/
<foreach collection="ids" item="id" open="id in (" close=")" separator=",">
${id}
</foreach>
</if>
</where>
</select>
▶如果传入的参数是List,只需把parameterType,if中的条件,以及collection的值改为list即可
9 mybatis与hibernate的区别【面试题】
Mybatis技术特点:
好处:
1、 通过直接编写SQL语句,可以直接对SQL进行性能的优化;
2、 学习门槛低,学习成本低。只要有SQL基础,就可以学习mybatis,而且很容易上手;
3、 由于直接编写SQL语句,所以灵活多变,代码维护性更好。
缺点:
4、 不能支持数据库无关性,即数据库发生变更,要写多套代码进行支持,移植性不好。
a) Mysql:limit
b) Oracle:rownum
5、 需要编写结果映射。
Hibernate技术特点:
好处:
1、 标准的orm框架,程序员不需要编写SQL语句。
2、 具有良好的数据库无关性,即数据库发生变化的话,代码无需再次编写。
a) 以后,mysql数据迁移到oracle,只需要改方言配置
缺点:
3、 学习门槛高,需要对数据关系模型有良好的基础,而且在设置OR映射的时候,需要考虑好性能和对象模型的权衡。
4、 程序员不能自主的去进行SQL性能优化。
Mybatis应用场景:
需求多变的互联网项目,例如电商项目。
Hibernate应用场景:
需求明确、业务固定的项目,例如OA项目、ERP项目等。
10 多表查询的模型设置
10.1 专门做一个扩展类继承其中一个表的模型,再将格外涉及到的另一个表的字段写到扩展类中,查询返回该扩展类
■扩展类
public class OrdersExt extends Orders{
private String username;
private String address;
}
■SQL
<select id="findOrderById" resultType="ordersExt">
SELECT o.*,u.username,u.address
FROM
user u,orders o
WHERE
o.user_id=u.id AND o.id=#{id}
</select>
10.2 模型类中嵌套另一个模型类
■模型类
public class Orders {
private Integer id; ▶模型中本来的字段
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user; ▶嵌套查询涉及到的另一个模型
}
■SQL
<resultMap id="orderResultMap" type="orders">
<id column="id" property="id"></id> ▶本来的模型(可以发现属性中全用id标签并不影响,具体作用以后再看看补充)
<id column="note" property="note"></id>
<id column="number" property="number"></id>
<id column="createtime" property="createtime"></id>
<association property="user" javaType="user"> ▶嵌套的模型,使用association标签。property是嵌套的变量名,javaType是对应的模型类名
<id column="user_id" property="id"></id>
<id column="username" property="username"></id>
<id column="address" property="address"></id>
</association>
</resultMap>
<select id="findOrderById2" resultMap="orderResultMap"> ▶原有的输出映射改为前面设置的map
SELECT o.*,u.username,u.address
FROM
user u,orders o
WHERE
o.user_id=u.id AND o.id=#{id}
</select>
10.3 模型中嵌套另一个模型的链表(一对多的关系。通过订单id来查询订单明细和对应的用户,订单id可以找到多条订单,每一条订单又可以对应多条订单明细)
■模型类
在10.2中的Orders中添加一个属性用于表示多条订单明细
//订单明细
private List<Orderdetail> orderdetails;
■SQL(比10.2中多几句)
<resultMap id="orderResultMap3" type="orders">
<id column="id" property="id"></id>
<id column="note" property="note"></id>
<id column="number" property="number"></id>
<id column="createtime" property="createtime"></id>
<association property="user" javaType="user"> ▶嵌套类的类型用javaType
<id column="user_id" property="id"></id>
<id column="username" property="username"></id>
<id column="address" property="address"></id>
</association>
<collection property="orderdetails" ofType="orderDetail"> ▶链表中元素的类型用ofType
<id column="datail_id" property="id"></id>
<id column="items_id" property="itemsId"></id>
<id column="items_num" property="itemsNum"></id>
</collection>
</resultMap>
<select id="findOrderById3" resultMap="orderResultMap3">
select o.*,u.username,u.address,od.id detaile_id,od.items_id,od.items_num
from orders o,user u,orderdetail od
where o.user_id = u.id and o.id=od.orders_id and o.id=#{id}
</select>
10.4 模型中不断嵌套:
★SQL语句(查找所有用户购买的商品)
<resultMap id="userRslMap" type="user"> ▶2.该user类
<!--user属性-->
<id column="id" property="id"></id> ▶user中的属性
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<collection property="orderList" ofType="orders"> ▶3.user类中嵌套一个订单orders链表(嵌套集合,使用collection,集合元素使用ofType)
<id column="order_id" property="id"></id>
<result column="number" property="number"></result>
<result column="createtime" property="createtime"></result>
<result column="note" property="note"></result>
<collection property="orderdetails" ofType="orderDetail"> ▶4.订单order中继续嵌套一个订单明细orderDetail链表(写法同上)
<id column="detail_id" property="id"></id>
<result column="items_id" property="itemsId"></result>
<result column="items_num" property="itemsNum"></result>
<association property="items" javaType="items"> ▶5.orderDetail中继续嵌套一个物品items类,表示商品信息(类嵌套,使用association,元素使用javaType)
<id column="items_id" property="id"></id>
<result column="name" property="name"></result>
<result column="price" property="price"></result>
<result column="detail" property="detail"></result>
</association>
</collection>
</collection>
</resultMap>
<select id="findUserAndOrderInfo" resultMap="userRslMap"> ▶1.该语句返回的是一个user链表(如果传入特定的用户id则返回一个用户user,后面要修改参数)
SELECT
u.id,
u.username,
u.address,
o.id order_id,
o.number,
o.createtime,
o.note,
od.id detail_id,
od.items_id,
od.items_num,
it.name,
it.price,
it.detail
FROM ▶关联四张表
user u, ▶用户表
orders o, ▶订单表
orderdetail od, ▶订单明细表
items it ▶物品表
WHERE
o.user_id=u.id
AND o.id=od.orders_id
and od.items_id=it.id
</select>
10.5 总结
resultType:将查询结果按照sql列名pojo属性名一致性映射到pojo中。
resultMap:使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association:将关联查询信息映射到一个pojo对象中。
collection:将关联查询信息映射到一个list集合中。
11 懒加载
延迟加载又叫懒加载,也叫按需加载。也就是说先加载主信息,在需要的时候,再去加载从信息。在mybatis中,resultMap标签的association标签和collection标签具有延迟加载的功能。
■SQL中的语句
<resultMap id="orderLazyloadingRslMap" type="orders">
<id column="id" property="id"></id>
<result column="note" property="note"></result>
<result column="number" property="number"></result>
<result column="createtime" property="createtime"></result>
<!--配置懒查询-->
<association property="user" select="com.dm.mapper.UserMapper.findUserById" column="user_id"></association> ▶写法:设置user为懒加载,其结果由select中的方法来执行(必须单独写一个该查询),该查询的参数由column来提供,其值就是之前查找出的列名
</resultMap>
<select id="findOrderAndUserByLazyloading" resultMap="orderLazyloadingRslMap">
SELECT * FROM orders
</select>
其上的写法并不能达到懒加载,运行以后语句仍然是直接执行懒加载中的select,需要在mybatis的配置文件中开启懒加载才会有效(运行以后会发现只有在打印订单的用户时才会调用查找用户的语句)
★SqlMapConfig.xml(在configuration下添加配置开启懒加载)
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
12 缓存
Mybatis的缓存,包括一级缓存和二级缓存,一级缓存是默认使用的。二级缓存需要手动开启。
12.1 一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
每一个SqlSession有一个一级缓存,用session做一次查询时,如果中间没有别的语句,下一次查询会直接取出这次的结果,故两次查询只会有一条查询语句;
但是一旦查询之后有其他非查询语句commit后,一级缓存就会被清空,下次的查找会重新执行SQL(★测试好像没有commit只是save语句未生效,select仍然会重新查找)
■测试语句
public void test7(){
SqlSession session=sessionFactory.openSession();
UserMapper userMapper=session.getMapper(UserMapper.class);
userMapper.findUserById(1);
//userMapper.save(new User("小马","1",null,"广东")); //▶释放该语句则一级缓存清空,下一次同样的查询将重新查找
userMapper.findUserById(1); //▶第二次查询同一个用户时命中缓存,不再执行SQL(Cache Hit Ratio)
}
12.2 二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。
二级缓存由所有的session共用,需要单独设置。某一个session关闭以后,二级缓存就开始生效;任何一个session提交了非查询语句后,二级缓存会被清空
★配置文件开启二级缓存:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<!--▶开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
★mapper中添加配置(UserMapper.xml,mapper下添加)▶对应的模型类需要实现序列化◀
<!--配置二级缓存。其中参数type不写,默认使用mybatis自带的缓存技术,perpetualCache-->
<cache></cache> ▶写在对应的mapper中即表示该类型开启了二级缓存
■测试代码
public void test8(){
SqlSession session1=sessionFactory.openSession();
SqlSession session2=sessionFactory.openSession();
SqlSession session3=sessionFactory.openSession();
UserMapper userMapper1=session1.getMapper(UserMapper.class);
UserMapper userMapper2=session2.getMapper(UserMapper.class);
UserMapper userMapper3=session3.getMapper(UserMapper.class);
User user1=userMapper1.findUserById(1);
System.out.println(user1);
session1.close(); //▶关闭session1,二级缓存开启
//userMapper3.save(new User("BB","1",null,"北京")); //▶释放代码,session3提交,缓存清空
//session3.commit();
User user2=userMapper2.findUserById(1); //▶session2第二次执行相同的查找,命中cache,不再执行查找语句
System.out.println(user2);
}
12.3 整合ehcache
Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式。Ehcache是一个分布式的缓存框架
Cache是一个接口,它的默认实现是mybatis的PerpetualCache。如果想整合mybatis的二级缓存,那么实现Cache接口即可。
●导入jar(目录:F:\BaiduNetdiskDownload\2019年4月黑马程序员教程\01-黑马IDEA版本Java基础+就业课程\4.框架\04.MyBatis\资料\ehcache2.6.5.zip)
导入ehcache-core-2.6.5.jar和mybatis-ehcache-1.0.2.jar
●修改mapper中的二级缓存配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
●在src目录下添加ehcache配置文件(之前导jar的压缩包里有例子)
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
</ehcache>
参数说明
maxElementsInMemory :设置基于内存的缓存中可存放的对象最大数目
eternal:设置对象是否为永久的,true表示永不过期,此时将忽略
timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
diskPersistent 当jvm结束时是否持久化对象 true false 默认是false
diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
●用之前的测试例子测试即可
●二级缓存应用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
局限性
Mybatis二级缓存对细粒度的数据,缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。
12.4 其他配置
<select id="findUserById" parameterType="int" resultType="user" useCache="false"> ▶禁用指定方法二级缓存
SELECT * FROM USER WHERE id=#{?}
</select>
<insert id="save" parameterType="user" flushCache="false"> ▶默认情况下插入更新删除都会清空二级缓存,flushCache为true。将其设为false,对应的方法操作语句就不会清空二级缓存了
Insert into user (username,sex,birthday,address) value (#{username},#{sex},#{birthday},#{address})
</insert>
DOWN 返回