1、数据模型分析
2、一对一映射(结果类型使用resultType)
3、一对一映射(使用resultMap)---对上个案例的补充
4、一对多映射
5、延迟加载
6、查询缓存概述
7、一级缓存
8、二级缓存
9、整合ehcache
10、spring整合mybatis
1、数据模型分析 <--返回目录
1)明确每张表存储的信息
2)明确每张表中关键字段(主键、外键、非空)
3)明确数据库中表与表之间的外键关系
4)明确业务中表与表的关系(建立在具体的业务)
2、一对一映射(结果类型使用resultType) <--返回目录
需求:查询订单信息,关联查询用户信息(主信息:订单信息,从信息:用户信息)
主信息:orders表 从信息:user表
sql 脚本
create database db_mybatis_01 default character set utf8; use db_mybatis_01; ------------ user ------------ CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` date DEFAULT NULL COMMENT '生日', `sex` char(1) DEFAULT NULL COMMENT '性别', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8; insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'王五',NULL,'2',NULL), (2,'张三','2014-07-10','1','北京市'), (3,'张小明',NULL,'1','河南郑州'), (4,'陈小明',NULL,'1','河南郑州'), (5,'张三丰',NULL,'1','河南郑州'), (6,'陈小明',NULL,'1','河南郑州'), (7,'王五',NULL,NULL,NULL); ------------ orders ------------ CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '下单用户id', `number` varchar(32) NOT NULL COMMENT '订单号', `createtime` datetime NOT NULL COMMENT '创建订单时间', `note` varchar(100) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`), CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; insert into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL), (4,1,'1000011','2015-02-03 13:22:41',NULL), (5,2,'1000012','2015-02-12 16:13:23',NULL);
实体类 User
public class User { private int id; private String username; // 用户名 private Date birthday; // 生日 private String sex; // 性别 private String address; // 地址 // getXxx和setXxx }
实体类 Orders
public class Orders { private Integer id; private Integer userId; // 下单用户id private String number; // 订单号 private Date createtime; // 创建订单时间 private String note; // 备注 // getXxx和setXxx }
订单扩展类 OrdersExt
public class OrdersExt extends Orders { private String username; private String sex; // getter和setter方法 }
db.properties
db.driver=com.mysql.jdbc.Driver db.url=jdbc:mysql://localhost:3306/db_mybatis_01?useUnicode=true&characterEncoding=utf8 db.username=root db.password=
log4j.properties
# Global logging configuration log4j.rootLogger=DEBUG, stdout # 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
mybatis全局配置文件 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文件或者声明属性信息 <property name="db.username" value="123" /> --> <properties resource="db.properties"> </properties> <!-- <settings></settings> --> <!-- 自定义别名 --> <typeAliases> <!-- 单个别名定义 --> <!-- <typeAlias type="com.oy.mybatis.po.User" alias="user"/> --> <!-- 批量别名定义(推荐) --> <!-- package:指定包名称来为该包下的po类声明别名,默认的别名就是类名(首字母大小写都可) --> <package name="com.oy.domain" /> </typeAliases> <!-- 配置mybatis的环境信息,与spring整合后,该信息由spring来管理 --> <environments default="development"> <environment id="development"> <!-- 配置JDBC事务控制,由mybatis进行管理 --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源,采用mybatis连接池 --> <dataSource type="POOLED"> <property name="driver" value="${db.driver}" /> <property name="url" value="${db.url}" /> <property name="username" value="${db.username}" /> <property name="password" value="${db.password}" /> </dataSource> </environment> </environments> <!-- 加载映射文件 --> <mappers> <mapper resource="OrdersMapper.xml"/> <!-- 批量加载映射文件 --> <!-- <package name="com.oy.mybatis.mapper" /> --> </mappers> </configuration>
OrdersMapper
public interface OrdersMapper { public List<OrdersExt> findOrdersAndUser(); }
OrdersMapper.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.oy.mapper.OrdersMapper"> <select id="findOrdersAndUser" resultType="com.oy.domain.OrdersExt"> SELECT orders.`id`,orders.`user_id` userId,orders.`number`, user.`username`,user.`sex` FROM orders,user WHERE orders.`user_id` = user.`id` </select> </mapper>
测试类
package com.oy.test; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import com.oy.domain.OrdersExt; import com.oy.mapper.OrdersMapper; public class Test1 { private SqlSessionFactory sqlSessionFactory; @Before public void setUp() throws Exception { String resource = "SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersAndUser() throws Exception { // 创建OrdersMapper对象 SqlSession sqlSession = sqlSessionFactory.openSession(); OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class); List<OrdersExt> orderExtList = mapper.findOrdersAndUser(); for (OrdersExt o : orderExtList) { System.out.println(o.getId() + "--" + o.getUserId() + "--" + o.getNumber() + "--" + o.getUsername() + "--" + o.getSex()); } sqlSession.close(); } }
注意:由于实体类Orders中属性userId对应表列名为user_id,所以sql语句中使用了别名。
也可以通过定义resultMap来实现上面的问题:实体类Orders中属性userId对应表列名为user_id。
<resultMap type="com.oy.domain.OrdersExt" id="RstMap"> <id column="id" property="id"/> <id column="user_id" property="userId"/> <id column="number" property="number"/> <id column="username" property="username"/> <id column="sex" property="sex"/> </resultMap> <select id="findOrdersAndUser" resultMap="RstMap"> SELECT orders.`id`,orders.`user_id`,orders.`number`, user.`username`,user.`sex` FROM orders,user WHERE orders.`user_id` = user.`id` </select>
3、一对一映射(使用resultMap)---对上个案例的补充 <--返回目录
扩展类
public class OrdersExt extends orders{ private User user; //getter和setter方法 }
映射文件OrdersMapper.xml
<resultMap type="com.oy.domain.OrdersExt" id="RstMap"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="username" property="user.username"/> <result column="sex" property="user.sex"/> </resultMap> <select id="findOrdersAndUser" resultMap="RstMap"> SELECT orders.`id`,orders.`user_id`,orders.`number`, user.`username`,user.`sex` FROM orders,user WHERE orders.`user_id` = user.`id` </select>
另外,也可以使用<association>来实现一对一映射
<resultMap type="com.oy.domain.OrdersExt" id="RstMap"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <!--用户信息(一对一)--> <association property="user" javaType="com.oy.domain.User"> <id column="user_id" property="id"/> //不写不会报错,但是会影响性能 <result column="username" property="username"/> <result column="sex" property="sex"/> </association> </resultMap> <select id="findOrdersAndUser" resultMap="RstMap"> SELECT orders.`id`,orders.`user_id`,orders.`number`, user.`username`,user.`sex` FROM orders,user WHERE orders.`user_id` = user.`id` </select>
4、一对多映射 <--返回目录
需求:查询用户信息,关联查询订单信息(一个用户有多个订单,所以是一对多)
扩展类
public class UserExt extends User{ private List<Orders> orderList; //getter和setter方法 }
映射文件
<!-- 查询所有用户 --> <resultMap type="com.oy.domain.UserExt" id="userExtRst"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="birthday" property="birthday"/> <result column="sex" property="sex"/> <result column="address" property="address"/> <collection property="orderList" ofType="com.oy.domain.Orders"> <id column="ordersId" property="id"/> <result column="createtime" property="createtime"/> <result column="number" property="number"/> </collection> </resultMap> <select id="findAll" resultMap="userExtRst"> select user.id,user.username,user.birthday,user.sex,user.address, orders.id ordersId,orders.createtime,orders.number from user,orders where user.id=orders.user_id </select>
5、延迟加载 <--返回目录
什么是延迟加载
- 延迟加载又叫懒加载,也叫按需加载。也就是说先加载主信息,在需要的时候,再去加载从信息。
- 在mybatis中,resultMap标签的association标签和collection标签具有延迟加载的功能。
需求:查询订单,关联用户,使用懒加载
mybatis全局配置文件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文件或者声明属性信息 <property name="db.username" value="123" /> --> <properties resource="db.properties"></properties> <settings> <!-- 开启延迟加载,默认值为true --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 设置积极的懒加载,默认值为true --> <setting name="aggressiveLazyLoading" value="false"/> </settings> <!-- 自定义别名 --> <typeAliases> <!-- 单个别名定义 --> <!-- <typeAlias type="com.oy.mybatis.po.User" alias="user"/> --> <!-- 批量别名定义(推荐) --> <!-- package:指定包名称来为该包下的po类声明别名,默认的别名就是类名(首字母大小写都可) --> <package name="com.oy.domain" /> </typeAliases> <!-- 配置mybatis的环境信息,与spring整合后,该信息由spring来管理 --> <environments default="development"> <environment id="development"> <!-- 配置JDBC事务控制,由mybatis进行管理 --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源,采用mybatis连接池 --> <dataSource type="POOLED"> <property name="driver" value="${db.driver}" /> <property name="url" value="${db.url}" /> <property name="username" value="${db.username}" /> <property name="password" value="${db.password}" /> </dataSource> </environment> </environments> <!-- 加载映射文件 --> <mappers> <!-- 批量加载映射文件 --> <package name="com.oy.mapper" /> </mappers> </configuration>
实体类 Orders
public class Orders { private Integer id; private Integer userId; private String number; private Date createtime; private String note; private User user; // getXxx和setXxx }
实体类 User
public class User { private int id; private String username; // 用户名 private Date birthday; // 生日 private String sex; // 性别 private String address; // 地址 // getXxx和setXxx }
OrdersMapper 类
public interface OrdersMapper { public List<Orders> findOrdersLazyLoadingUser(); }
UserMapper 类
public interface UserMapper { public User findUserById(int id); }
OrdersMapper.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.oy.mapper.OrdersMapper"> <!-- 查询订单,关联用户,使用懒加载 --> <resultMap type="orders" id="Rst"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <association property="user" select="com.oy.mapper.UserMapper.findUserById" column="user_id"/> </resultMap> <select id="findOrdersLazyLoadingUser" resultMap="Rst"> select * from orders </select> </mapper>
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.oy.mapper.UserMapper"> <!-- 根据用户ID查询用户信息 --> <select id="findUserById" parameterType="int" resultType="com.oy.domain.User"> SELECT * FROM USER WHERE id =#{id} </select> </mapper>
测试
package com.oy.test; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import com.oy.domain.Orders; import com.oy.mapper.OrdersMapper; public class Test1 { private SqlSessionFactory sqlSessionFactory; @Before public void setUp() throws Exception { String resource = "SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindOrdersLasyLoadingUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class); List<Orders> list = mapper.findOrdersLazyLoadingUser(); System.out.println("list.size="+list.size()); for (Orders o : list) { System.out.println(o.getUser()); } sqlSession.close(); } }
打印结果
DEBUG [main] - ==> Preparing: select * from orders DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 3 list集合的大小:3 //3表示查询结果是3个Orders对象 DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id =? DEBUG [main] - ==> Parameters: 1(Integer) DEBUG [main] - <== Total: 1 User [id=1, username=王五, birthday=null, sex=2, address=null] User [id=1, username=王五, birthday=null, sex=2, address=null] //第二次直接从一级缓存中读取数据,不发送sql语句 DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id =? DEBUG [main] - ==> Parameters: 10(Integer) DEBUG [main] - <== Total: 1 User [id=10, username=张小三, birthday=2014-07-10, sex=1, address=北京市]
6、查询缓存概述 <--返回目录
* Mybatis的缓存,包括一级缓存和二级缓存
* 一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。
一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
* 二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。
一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
* 一级缓存是默认使用的。二级缓存需要手动开启。
7、一级缓存 <--返回目录
验证一级缓存
@Test public void test()throws Exception{ SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); //第一次查询 User user1 = mapper.findUserById(1); System.out.println("user1"); //第二次查询 User user2 = mapper.findUserById(1); //第二次查询不会发送sql语句 System.out.println("user2"); sqlSession.close(); }
如果进行增删改操作后执行commit将清空一级缓存
@Test public void test()throws Exception{ SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); //第一次查询 User user1 = mapper.findUserById(1); System.out.println("user1"); //执行添加用户操作 mapper.insertUser(user1); //执行commit,会将一级缓存清空 sqlsession.commit(); //第二次查询 User user2 = mapper.findUserById(1); //会发送sql语句 System.out.println("user2"); sqlSession.close(); }
8、二级缓存 <--返回目录
* 二级缓存需要手动开启:先开总开关,然后在mapper中开启(谁用谁开启)
- 开启二级缓存的总开关 在SQLMapConfig.xml
<seting name="cacheEnabled" value="true"/>
- 在mapper映射文件中开启二级缓存
<cache/>使用默认的缓存实现类PerpetualCache
* 要使用二级缓存,除了手动开启,还要序列化实体类
public class User implements Serializable{}
* 测试代码:
@Test public void test() throws Exception { SqlSession sqlSession1 = SqlSessionFactory.openSession(); SqlSession sqlSession2 = SqlSessionFactory.openSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); //第一次查询 User user1 = mapper1.findUserById(1); System.out.println(user1); sqlSession1.close();//在close的时候,才会将数据写入到二级缓存中 //第二次查询 User user2 = mapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
* 测试代码二:
@Test public void test() throws Exception { SqlSession sqlSession1 = SqlSessionFactory.openSession(); SqlSession sqlSession2 = SqlSessionFactory.openSession(); SqlSession sqlSession3 = SqlSessionFactory.openSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); UserMapper mapper3 = sqlSession2.getMapper(UserMapper.class); //第一次查询 User user1 = mapper1.findUserById(1); System.out.println(user1); sqlSession1.close();//在close的时候,才会将数据写入到二级缓存中 //执行添加用户操作 mapper3.insertUser(user1); //执行comit,清空二级缓存 sqlSession.commit(); //第二次查询 User user2 = mapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
* 禁用缓存
默认值是true
<select id="" parameterType="" resultType="" userCache="false"> 禁用缓存userCache="false"
9、整合ehcache <--返回目录
* Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式。
* Ehcache是一个分布式的缓存框架。
* 常见分布式缓存框架:redis、memcached、ehcache
* 整合思路
Cache是一个接口,它的默认实现是mybatis的PerpetualCache。
如果想整合mybatis的二级缓存,那么实现Cache接口即可。
* 整合步骤
- 第一步:添加jar包:ehcache-core-2.6.5.jar和mybatis-ehcache-1.0.2.jar
- 第二步:设置映射文件中cache标签的type值为ehcache的实现类
<cache type="org.mybatis.caches.ehcache.EncacheCache"/>
- 第三步:添加ehcache的配置文件
在config下,创建ehcache.xml 不用记,用的时候拷贝
ehcache.xml的具体配置见"ehcache.xml的内容.jpg"
- 完成,可以使用了。
* 二级缓存的应用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,
这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
* 局限性
Mybatis二级缓存对细粒度的数据,缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,
此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,
因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中
10、spring整合mybatis <--返回目录
* 整合思路
1)数据源信息交给spring管理
2)SqlSessionFactory交给spring进行单例管理
3)由spring来管理原始dao的实现类或者mapper代理的代理类。
* jar包
- Mysql的驱动包
- Mybatis的核心包和依赖包
- Mybatis和spring的整合包
- Spring的包
- dbcp数据库连接池包
- 在config下,创建SqlMapConfig.xml 只需配置<typeAliases>
- 将db.properties和log4j.properties拷贝到config目录下。
- 在config下,创建spring目录,然后创建applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 引入配置文件 --> <context:property-placeholder location="db.properties"/> <!-- 配置数据源,使用dbcp连接池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${db.driver}" /> <property name="url" value="${db.url}" /> <property name="username" value="${db.username}" /> <property name="password" value="${db.password}" /> <property name="maxActive" value="10" /> <property name="maxIdle" value="5" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- mybatis的配置文件路径 --> <property name="configLocation" value="sqlMapConfig.xml"></property> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- mapper代理开发方式之批量mapper配置 --> <!-- bean的名字默认为mapper接口类名的首字母小写 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 指定批量mapper配置的包名 --> <property name="basePackage" value="cn.oy.mybatis.mapper"></property> //加载配置文件,指定扫描的包 <!-- 注入SqlSessionFactory,默认的,可以不写 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> </beans>
- 映射文件
<mapper namespace="com.oy.mapper.UserMapper"> <select id="selectUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> </mapper>
- Mapper接口:省略
- 测试代码
private ApplicationContext ctx; @Before public void setUp() throws Exception { ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml"); } @Test public void testFindUserById() { // 创建mapper对象 UserMapper userMapper = (UserMapper) ctx.getBean("userMapper"); // 调用mapper对象的方法 User user = userMapper.findUserById(1); System.out.println(user); }
---