1. 关联查询
1.1 多对一
PO类
public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
。。。
}
映射文件
<resultMap type="cn.com.model.Orders" id="userordermap">
<id property="id" column="id" />
<result property="userId" column="user_id" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
<association property="user" javaType="cn.com.model.User">
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
</resultMap>
<select id="findOrdersListResultMap" resultMap="userordermap">
SELECT orders.*, user.username, user.address
FROM orders, user
WHERE orders.user_id = user.id
</select>
多对一中,使用association 映射一方
<association property="user" javaType="cn.com.model.User">
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
“id” 会使用orders中的user_id 对id进行赋值
property : 多方中对应的一方的属性名
javaType:类型
1.2 一对多
PO类
public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
//订单明细
private List<Orderdetail> orderdetails;
。。。。
}
映射文件
<resultMap type="cn.com.model.Orders" id="userorderdetailmap">
<id property="id" column="id" />
<result property="userId" column="user_id" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
<association property="user" javaType="cn.com.model.User">
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
<collection property="orderdetails" ofType="cn.com.model.Orderdetail">
<id property="id" column="orderdetail_id" />
<result property="itemsId" column="items_id" />
<result property="itemsNum" column="items_num" />
</collection>
</resultMap>
<select id="findOrdersDetailList" resultMap="userorderdetailmap">
SELECT orders.*,user.username,user.address,
orderdetail.id orderdetail_id,orderdetail.items_id, orderdetail.items_num
FROM orders,user,orderdetail
WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id
</select>
一对多中,一方中会定义多方的一个集合,在映射文件中使用collection映射。
<collection property="orderdetails" ofType="cn.com.model.Orderdetail">
<id property="id" column="orderdetail_id" />
<result property="itemsId" column="items_id" />
<result property="itemsNum" column="items_num" />
</collection>
property:一方中的多方集合属性名称。
ofType:指定关联查询的结果集中的对象类型即List中的对象类型。
1.3 resultMap继承
上面的resultMap userorderdetailmap 可以继承userordermap ,复用重复部分
<resultMap type="cn.com.model.Orders" id="userorderdetailmap" extends="userordermap">
<collection property="orderdetails" ofType="cn.com.model.Orderdetail">
<id property="id" column="orderdetail_id" />
<result property="itemsId" column="items_id" />
<result property="itemsNum" column="items_num" />
</collection>
</resultMap>
resultMap 中用 extends=”父resultMap” 可复用已有的resultMap中的映射配置
1.4 多对多
需要关联查询映射的信息是:用户、订单、订单明细、商品信息
订单:一个用户对应多个订单,使用collection映射到用户对象的订单列表属性中
订单明细:一个订单对应多个明细,使用collection映射到订单对象中的明细属性中
商品信息:一个订单明细对应一个商品,使用association映射到订单明细对象的商品属性中
PO类
public class User {
private int id;
private String username;
private String sex;
private Date birthday;
private String address;
//一个用户对应多个订单
private List<Orders> orders = new ArrayList<Orders>();
}
public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
//订单明细
private List<Orderdetail> orderdetails;
。。
}
public class Orderdetail {
private Integer id;
private Integer ordersId;
private Integer itemsId;
private Integer itemsNum;
private Items items;
。。。。
}
映射文件
<resultMap type="cn.com.model.User" id="userOrderListResultMap">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<collection property="orders" ofType="cn.com.model.Orders">
<id column="id" property="id"/>
<result property="number" column="number"/>
<association property="user" javaType="cn.com.model.User">
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
<collection property="orderdetails" ofType="cn.com.model.Orderdetail">
<id column="orderdetail_id" property="id"/>
<result property="ordersId" column="id"/>
<result property="itemsId" column="items_id"/>
<result property="itemsNum" column="items_num"/>
<association property="items" javaType="cn.com.model.Items">
<id column="items_id" property="id"/>
<result column="items_name" property="name"/>
<result column="items_detail" property="detail"/>
</association>
</collection>
</collection>
</resultMap>
<select id="findAll" resultMap="userOrderListResultMap">
SELECT orders.*, USER .username, USER .address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
items.name items_name,
items.detail items_detail
FROM orders, USER,orderdetail,items
WHERE orders.user_id = USER .id
AND orders.id = orderdetail.orders_id
AND orderdetail.items_id = items.id
</select>
2. 延迟加载
需要查询关联信息时,使用mybatis延迟加载特性可有效的减少数据库压力,首次查询只查询主要信息,关联信息(如订单用户关系,可先查询订单,需要用户信息)获取时再加载。
2.1 打开延迟加载开关
在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载 | true,false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载 | true ,false | true |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
2.2 多对一延迟加载
PO类
一个用户可以有多个订单,多个订单归属一个用户
订单与用户是多对一关系,在订单实体类中增加User的属性
public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
。。。。。
}
映射文件
- Orders映射文件
在Orders的映射文件中,通过association 配置关联属性,此处不在配置User的各个属性映射
增加select 和 colum属性配置
select=”cn.com.model.UserDao.findUserById”
在association 中配置select属性,指定懒加载关联查询使用的statement,findUserById在user.xml中,所以此处加上命名空间
column=”user_id”
在association 中配置column属性,指定懒加载关联查询使用的statement中需要传入的值
<?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="cn.com.dao.OrdersDao">
<resultMap type="cn.com.model.Orders" id="orderLazyUser">
<id property="id" column="id" />
<result property="userId" column="user_id" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
<association property="user" javaType="cn.com.model.User"
select="cn.com.model.UserDao.findUserById" column="user_id">
</association>
</resultMap>
<select id="findOrdersLazyLoadUser" resultMap="orderLazyUser">
SELECT id,user_id,number,createtime,note FROM orders
</select>
</mapper>
- User映射文件
此处 的findUserById 在orders映射文件中使用到
<mapper namespace="cn.com.model.UserDao">
<select id="findUserById" parameterType="int" resultType="cn.com.model.User">
SELECT * FROM USER WHERE id=#{value}
</select>
</mapper>
测试类
@Test
public void findOrdersLazyLoadUser() throws Exception{
String resource = "sqlmapconfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersDao ordersDao = sqlSession.getMapper(OrdersDao.class);
List<Orders> list = ordersDao.findOrdersLazyLoadUser();
for(Orders orders:list){
System.out.println(orders.getUser().getUsername());
}
sqlSession.close();
}
此处在System.out.println(orders.getUser().getUsername());才会查询数据库,加载User的值
3. 查询缓存
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
3.1 一级缓存
一级缓存区域是根据SqlSession为单位划分的。
同一个SqlSession每次查询会先从缓存区域找,如果找不到再从数据库查询,查询到数据将数据写入缓存。下一次再次执行相同的查询时,则会从缓存中取出,不再查询数据库。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
一级缓存是SQLSession级别的缓存。默认是开启的。
- 测试一级缓存
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//第二次查询,由于是同一个session则不再向数据发出语句直接从缓存取出
User user2 = userMapper.findUserById(1);
System.out.println(user2);
//关闭session
session.close();
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//在同一个session执行更新
user1 .setUsername("xxx");
userMapper.updateUser(user1 );
session.commit();
//第二次查询,虽然是同一个session但是由于执行了更新操作session的缓存被清空,这里重新发出sql操作
User user2 = userMapper.findUserById(1);
System.out.println(user2);
3.2 二级缓存
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域。
每一个namespace会有一个自己的二级缓存,不同的 namespace用不同的二级缓存。
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存
在SqlMapConfig.xml设置二级缓存的总开关默认是开启的,但是建议还是配置开启。
3.2.1 开启二级缓存
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
配置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled | 对在此配置文件下的所有cache 进行全局性开/关设置 | true,false | true |
mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。
<mapper namespace="cn.com.dao.OrdersDao">
<!-- 开启本 mapper的 namespace下的二级缓存-->
<cache type=""/>
type指定实现mybatis cache 接口的实现类 类名,默认是org.apache.ibatis.cache.impl.PerpetualCache
3.2.2 pojo类实现序列化接口
为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一样在内存。
public class Orders implements Serializable{
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//用户信息
private User user;
。。
}
3.2.3 测试1
String resource = "sqlmapconfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
OrdersDao ordersDao1 = sqlSession1.getMapper(OrdersDao.class);
//第一次查询,缓存中没数据,请求数据库查询
Orders orders1 = ordersDao1.findById(3);
//sqlsession关闭
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
OrdersDao ordersDao2 = sqlSession2.getMapper(OrdersDao.class);
//第二次查询,缓存中有数据,从缓存中取出数据,不再请求数据库查询
Orders orders2 = ordersDao2.findById(3);
sqlSession2.close();
3.2.4 测试2
String resource = "sqlmapconfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
OrdersDao ordersDao1 = sqlSession1.getMapper(OrdersDao.class);
//第一次查询,缓存中没数据,请求数据库查询
Orders orders1 = ordersDao1.findById(3);
//sqlsession关闭
sqlSession1.close();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
OrdersDao ordersDao3 = sqlSession3.getMapper(OrdersDao.class);
//第二次查询,缓存中有数据,从缓存中取出数据,不再请求数据库查询
Orders orders3 = ordersDao3.findById(3);
orders3.setCreatetime(new Date());
ordersDao3.updateOrder(orders3);
sqlSession3.commit();
//执行提交,清空OrderMapper下边的二级缓存,防止脏读
sqlSession3.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
OrdersDao ordersDao2 = sqlSession2.getMapper(OrdersDao.class);
//第三次查询,缓存中没有数据,请求数据库查询
Orders orders2 = ordersDao2.findById(3);
sqlSession2.close();
3.2.5 特定查询禁用二级缓存(useCache配置)
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findById" useCache="false" parameterType="int" resultMap="orderLazyUser">
3.2.6 刷新缓存(清空缓存)
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache=”true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
3.3 mybatis整合ehcache
ehcache是一个分布式缓存框架。我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)
不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统 开发。所以要使用分布式缓存对缓存数据进行集中管理。
mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
3.3.1 整合原理
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
mybatis默认实现cache类是:
package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
public String getId() {
return id;
}
public int getSize() {
return cache.size();
}
public void putObject(Object key, Object value) {
cache.put(key, value);
}
public Object getObject(Object key) {
return cache.get(key);
}
public Object removeObject(Object key) {
return cache.remove(key);
}
public void clear() {
cache.clear();
}
public ReadWriteLock getReadWriteLock() {
return null;
}
public boolean equals(Object o) {
if (getId() == null) throw new CacheException("Cache instances require an ID.");
if (this == o) return true;
if (!(o instanceof Cache)) return false;
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
public int hashCode() {
if (getId() == null) throw new CacheException("Cache instances require an ID.");
return getId().hashCode();
}
}
3.3.2 加入ehcache包
下载地址:http://download.csdn.net/detail/hxskmx/8802683
ehcache实现mybatis cache接口 实现类
3.3.3 mybatis整合ehcache
配置mapper中cache中的type为ehcache对cache接口的实现类型。
<mapper namespace="cn.congxing.model.User">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
3.3.4 加入ehcache的配置文件
在classpath下配置ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
4. generator代码生成
4.1 下载generator
http://download.csdn.net/detail/hxskmx/8802897
4.2 配置文件
generatorConfig.xml
<?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="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="123">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:orcl" userId="demo" password="demo">
</jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="cn.congxing.model"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.congxing.model"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.congxing.dao"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
<!-- <table schema="" tableName="sys_user"></table>
<table schema="" tableName="sys_role"></table>
<table schema="" tableName="sys_permission"></table>
<table schema="" tableName="sys_user_role"></table>
<table schema="" tableName="sys_role_permission"></table> -->
<!-- 有些表的字段需要指定java类型
<table schema="" tableName="">
<columnOverride column="" javaType="" />
</table> -->
</context>
</generatorConfiguration>
4.3 生成代码
package cn.congxing.test;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
public class GeneratorSqlmap {
public void generator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("config/generatorConfig.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);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}