MyBatis泛览二(扩展加小案例)
关联查询
基本信息
图片1.png
- user和orders:
- User 与orders:一个用户可以创建多个订单,一对多
- Orders 与 user:多个订单只由一个用户创建,多对一
- orders和orderdetail:
- Orders 与 orderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
- orderdetail 与orders:多个订单明细包括在一个订单中, 多对一
- orderdetail和items:
- Orderdetail 与 items:多个订单明细只对应一个商品信息,多对一
- Items 与 orderdetail:一个商品可以包括在多个订单明细 ,一对多
需求
根据商品ID查找定单信息,包括用户名和地址
#查找id为10的所有定单
SELECT
orders.id, orders.number,orders.createtime,orders.note,`user`.username,`user`.address
FROM
orders ,`user`
WHERE
orders.user_id = `user`.id AND `user`.id = 10;
一对一resultType实现
复杂查询时,单表对应的po类已不能满足输出结果集的映射。所以要根据需求建立一个扩展类来作为resultType的类型。
#查找某个定单id的信息,包括用户名字和地址
SELECT o.*,u.username,u.address FROM orders o,user u
WHERE o.user_id = u.id AND o.id = 3
写个订单的扩展类
public class OrdersExt extends Orders{
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "OrdersExt{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}' + super.toString();
}
}
订单接口类:
public interface OrderMapper {
public OrdersExt findOrderById(int id);
}
<?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">
<!-- 此时查询出来不指订单表的数据,还需要user表的相关数据,所以写了一个扩展类 -->
<mapper namespace="com.zengqiang.mapper.OrderMapper">
<select id="findOrderById" parameterType="int" resultType="ordersExt">
SELECT
o.*,u.username,u.address
FROM
orders o,user u
WHERE
o.user_id = u.id
AND o.id = #{id}
</select>
</mapper>
加载映射文件
<mappers>
<package name="com.zengqiang.mapper"></package>
</mappers>
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"/>
<!--配置允许懒加载-->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<!--配置别名-->
<typeAliases>
<!--<typeAlias type="com.zengqiang.model.User" alias="user"></typeAlias>-->
<!--指定包名,别名就是类名,第一个小写 User 别名就是user-->
<package name="com.zengqiang.model"></package>
<package name="com.zengqiang.vo"></package>
</typeAliases>
<!-- 配置mybatis的环境信息 -->
<environments default="development">
<environment id="development">
<!-- 配置JDBC事务控制,由mybatis进行管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,采用dbcp连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${name}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--告诉mybatis加载映射文件-->
<mappers>
<!--<mapper resource="com/zengqiang/sqlmap/User.xml"></mapper>-->
<!--第一种:写映射文件的名字-->
<!--<mapper resource="com/zengqiang/mapper/UserMapper.xml"></mapper>-->
<!--第二种:写类名,一定要有个映射文件与之对应
如果没有,那么在UserMapper要声明注解-->
<!--<mapper class="com.zengqiang.mapper.UserMapper"></mapper>-->
<!--第三种:可以写包名-->
<package name="com.zengqiang.mapper"></package>
</mappers>
</configuration>
测试类:
/**
* 一对一 : 写个定单的扩展类
* @throws IOException
*/
@Test
public void test6() throws IOException {
OrderMapper mapper = session.getMapper(OrderMapper.class);
OrdersExt ordersExt = mapper.findOrderById(3);
System.out.println(ordersExt);
}
一对一 resultMap实现
此种方法不需要书写扩展模型
<!-- =============== 一对一 =================-->
<!--如果模型里有模型,使用resultMap-->
<resultMap id="orderRslMap" type="orders">
<!-- 往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配置-->
<!-- 往orders的User匹配数据
模型里有模型,使用association来配置
property="user"中user对应orders中user属性
javaType="user"中user对应User.java这个模型
-->
<association property="user" javaType="user">
<id column="user_id" property="id"></id>
<id column="username" property="username"></id>
<id column="address" property="address"></id>
</association>
</resultMap>
<select id="findOrderById2" parameterType="int" resultMap="orderRslMap">
SELECT
o.*,u.username,u.address
FROM
orders o,user u
WHERE
o.user_id = u.id
AND o.id = #{id}
</select>
一对多
<!-- ================一对多==================-->
<resultMap id="orderRslMap3" type="orders">
<!-- 往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>
<!-- 往orders的user匹配数据
模型里有模型,使用association来配置-->
<association property="user" javaType="user">
<id column="user_id" property="id"></id>
<id column="username" property="username"></id>
<id column="address" property="address"></id>
</association>
<!-- 一对多匹配: 往orders的orderdetails 匹配数据
注意:集合里类型使用ofType,而不javaType
-->
<collection property="orderDetails" ofType="orderDetail">
<id column="detail_id" property="id"></id>
<id column="items_id" property="itemsId"></id>
<id column="items_num" property="itemsNum"></id>
</collection>
</resultMap>
<select id="findOrderById3" parameterType="int" resultMap="orderRslMap3">
SELECT
o.*,
u.username,
u.address,
od.id detail_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>
懒加载
<!-- ============= 懒加载 ,懒加载需要在全局配置文件里面配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
才能懒加载生效
==================-->
<resultMap id="orderLazyloadingRslMap" type="orders">
<id column="id" property="id"/>
<result column="note" property="note"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<!--配置查询,select对应的是一个具体的方法,指的是,需要的时候才去调用这个方法查询特定懒加载
的数据,而上面的id note等则不会懒加载,会直接查出来
column:代表参数
返回值赋给property的值,即user属性
select:代表懒加载使用的方法
-->
<association property="user" select="com.zengqiang.mapper.UserMapper.findUserById" column="user_id"/>
</resultMap>
<select id="findOrderAndUserByLazyloading" resultMap="orderLazyloadingRslMap">
SELECT * FROM orders
</select>
多对对
<!-- 多对多==============查询用户信息及用户购买的商品信息============-->
<resultMap id="userRslMap" type="user">
<!-- 1.匹配user属性 -->
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="password" property="password"/>
<!--2.匹配user的orderList-->
<collection property="orderList" ofType="orders">
<id column="order_id" property="id"></id>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 3.匹配Orders里有orderDetails-->
<collection property="orderDetails" ofType="orderDetail">
<id column="detail_id" property="id"></id>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<!-- 4.配置定单详情的商品信息-->
<association property="items" javaType="items">
<id column="items_id" property="id"/>
<result column="name" property="name"/>
<result column="price" property="price"/>
<result column="detail" property="detail"/>
</association>
</collection>
</collection>
</resultMap>
<select id="findUserAndOrderInfo" resultMap="userRslMap">
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>
测试类:
public class Demo01 {
SqlSession session;
@Before
public void before() throws IOException {
System.out.println("before.....获取session");
// a)读取配置文件;
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//b)通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂。
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
session = sessionFactory.openSession();
}
@After
public void after(){
session.close();
}
/**
* 一对一 : 写个定单的扩展类
* @throws IOException
*/
@Test
public void test6() throws IOException {
OrderMapper mapper = session.getMapper(OrderMapper.class);
OrdersExt ordersExt = mapper.findOrderById(3);
System.out.println(ordersExt);
}
/**
* 一对一 : 模型里有模型
* @throws IOException
*/
@Test
public void test7() throws IOException {
OrderMapper mapper = session.getMapper(OrderMapper.class);
Orders order = mapper.findOrderById2(3);
System.out.println(order);
System.out.println(order.getUser());
}
/**
* 一对多 : 模型里有集合
* @throws IOException
*/
@Test
public void test8() throws IOException {
OrderMapper mapper = session.getMapper(OrderMapper.class);
Orders order = mapper.findOrderById3(3);
System.out.println(order);
System.out.println(order.getUser());
System.out.println(order.getOrderDetails());
}
/**
* 多对多
* @throws IOException
*/
@Test
public void test9() throws IOException {
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.findUserAndOrderInfo();
for (User user : users){
System.out.println("用户信息:" + user);
for (Orders order : user.getOrderList()){
System.out.println("定单信息:" + order);
System.out.println("订单详情:");
for (OrderDetail od : order.getOrderDetails()){
System.out.println(od + ":" + od.getItems());
}
System.out.println("------------------------------");
}
}
}
/**
* 懒加载
* @throws IOException
*/
@Test
public void test10() throws IOException {
OrderMapper mapper = session.getMapper(OrderMapper.class);
List<Orders> list = mapper.findOrderAndUserByLazyloading();
for (Orders order : list){
System.out.println("订单信息:");
System.out.println(order);
System.out.println("订单所属的客户:");
//以下的数据,在调用getUser时候才利用com.zengqiang.mapper.UserMapper.findUserById去查询数据实现懒加载
System.out.println(order.getUser());
}
}
}
缓存
Mybatis的缓存,包括一级缓存和二级缓存,一级缓存是默认使用的。二级缓存需要手动开启。
一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是二级缓存区域。
图片2.png
一级缓存
![](https://img.haomeiwen.com/i1334027/ab9e6e706b2ec5e0.png)
@Test
public void test1() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//1.SqlSessionFactory会话工厂。
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
//2.会话
SqlSession session = sessionFactory.openSession();
//3.通过会话获取dao接口
UserMapper mapper = session.getMapper(UserMapper.class);
/**
* 如果上面不执行增删改,则不会重新执行sql语句
*
* 默认情况下,一级缓存,就是session级别缓存是开启
* 保存,删除,更新,一级缓存的数据会自动清空,下次查询,会执行sql语句
*/
User user1 = mapper.findUserById(1);
System.out.println("user1:" + user1);
//保存用户
mapper.save(new User("广东雨神","1",null,"广东"));
User user2 = mapper.findUserById(1);
System.out.println("user2:" + user2);
session.commit();
session.close();
}
二级缓存
![](https://img.haomeiwen.com/i1334027/48058a6080f56560.png)
> 首先在全局配置文件开启二级缓存
<settings>
<!--配置允许懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--允许开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
> 对应的Mapper.xml中配置二级缓存
<cache></cache>
对应的pojo模型要实现序列化接口
public class User implements Serializable {
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
private List<Orders> orderList;//一个用户有多张定单
public List<Orders> getOrderList() {
return orderList;
}
public void setOrderList(List<Orders> orderList) {
this.orderList = orderList;
}
public User() {
}
public User(String username, String sex, Date birthday, String address) {
this.username = username;
this.sex = sex;
this.birthday = birthday;
this.address = address;
}
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex
+ ", birthday=" + birthday + ", address=" + address + "]";
}
}
-
禁用指定方法二级缓存
图片4.png
-
刷新缓存
<!-- ===================第一天内容=======================-->
<!-- 默认情况下,插入,更新,删除,会清空二级缓存
默认情况下flushCache="true"
如果设置为false,插入,更新,删除就不会清空二级缓存
-->
<insert id="save" parameterType="user" flushCache="false">
INSERT INTO user (username,sex,birthday,address)
VALUE (#{username},#{sex},#{birthday},#{address})
</insert>
- 整合ehcache
Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式。Ehcache是一个分布式的缓存框架。
图片5.png
3.1 整合思路
Cache是一个接口,它的默认实现是mybatis的PerpetualCache。如果想整合mybatis的二级缓存,那么实现Cache接口即可.
![](https://img.haomeiwen.com/i1334027/d962905873922877.png)
3.2 添加jar包
![](https://img.haomeiwen.com/i1334027/8c9f4388bc389e80.png)
3.3 设置映射文件中cache标签
!-- 配置缓存
1.type不写,默认使用的是mybaits自带的缓存技术,perpetualCache
2.可以改成ehcache缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
3.4 在src下添加ehcache的配置文件
<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(先进先出)
3.5 应用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
3.6 局限性
Mybatis二级缓存对细粒度的数据,缓存实现不好。
场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。
逆向工程
简单点说,就是通过数据库中的单表,自动生成java代码。Mybatis官方提供了逆向工程可以针对单表自动生成mybatis代码(mapper.java\mapper.xml\po类)企业开发中,逆向工程是个很常用的工具。
下载逆向工程
https://github.com/mybatis/generator/releases/tag/mybatis-generator-1.3.2
使用步骤
- 创建简单的java项目
- 导入jar包,创建generator配置文件;
- 使用java类来执行逆向工程;
- 把生成的代码拷贝到项目中。
- 在正式项目中使用逆向工程生成的代码
- 创建generator配置文件
在classpath下,创建generator.xml配置文件:(文件内容可以从逆向工程的jar包中docs目录下的index.html中找到相关代码)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="mysqlTable" targetRuntime="MyBatis3">
<!-- 1.数据连接参数 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- 2.默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL
和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- 3.生成模型的位置 -->
<javaModelGenerator targetPackage="com.zengqiang.backoffice.domain" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 4.targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.zengqiang.backoffice.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- 5. targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.zengqiang.backoffice.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 6.要生成的表 -->
<table tableName="items"/>
<table tableName="orderdetail"/>
<table tableName="orders"/>
<table tableName="user"/>
</context>
</generatorConfiguration>
- 使用java类来执行逆向工程
需要导入mysql的驱动包和mybatis的逆向工程包
public class Generator {
public static void main(String[] args) throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("config/generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
}
- 把生成的代码拷贝到项目中
如果正式项目中已经有po类所在的包了,那么就只需要拷贝po类到指定包下就可以。
如果正式项目中没有po包,那么就把逆向工程中整个po类的包拷贝过去。
Mapper.xml和mapper.java的拷贝与po类一样。
图片6.png