<img src="http://student.kaikeba.com//assets/blue_logo-57d711624a.png" style="float:right;width:120px;padding-top:26px;" />
Mybatis基于XML和注解方式的开发应用专题
[TOC]
开发人员 OR 研发人员?
主要是使用成熟的框架去开发应用功能,还是使用JavaEE、JVM、并发编程、NIO/Netty等知识点实现编写自定义框架或者解决高并发场景下的非功能性需求,比如如何提高并发能力等?
如何进行接下来的学习呢?
他山之石,可以攻玉!!学习人家的框架,写出自己的框架。
摘自百度:
可以说,一个框架是一个 可复用 的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提供了上下文(Context)关系。因此构件库的大规模重用也需要框架。
我们用三层结构主要是使项目结构更清楚,分工更明确,有利于后期的维护和升级.
三层结构包含: 表现层,业务层,持久层
总体来说,设计模式分为 三类23种 :
工厂模式、抽象工厂模式、单例模式、原型模式、构建者模式
适配器模式、装饰模式、代理模式
、外观模式、桥接模式、组合模式、享元模式 模板方法模式、策略模式
、观察者模式、中介者模式、状态模式、责任链模式、命令模式、迭代器模式、访问者模式、解释器模式、备忘录模式 mybatis参考网址: http://www.mybatis.org/mybati...
Github源码地址: https://github.com/mybatis/my...
MyBatis 是一款优秀的 持久层框架 ,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集,它可以使用简单的 XML 或 注解 来配置和映射SQL信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
对象-关系映射(OBJECT/RELATIONALMAPPING,简称ORM),是随着面向对象的 软件开发方法 发展而产生的。用来把对象模型表示的对象映射到基于SQL 的关系模型数据库结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的 SQL 语句打交道,只需简单的操作实体对象的属性和方法 。ORM 技术是在对象和关系之间提供了一条桥梁,前台的对象型数据和数据库中的关系型的数据通过这个桥梁来相互转化。
对比项 | Mybatis | Hibernate |
---|---|---|
市场占有率 | 高 | 高 |
适合的行业 | 互联网 电商 项目 | 传统的(ERP CRM OA) |
性能 | 高 | 低 |
Sql灵活性 | 高 | 低 |
学习门槛 | 低 | 高 |
Sql配置文件 | 全局配置文件、映射文件 | 全局配置文件、映射文件 |
ORM | 半自动化 | 完全的自动化 |
数据库无关性 | 低 | 高 |
1、根据用户id查询一个用户信息
2、根据用户名称模糊查询用户信息列表
3、添加用户
<dependencies> <!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- mysql依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
<?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" /> <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="UserMapper.xml" /> </mappers> </configuration>
<?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="test"> </mapper>
public class User { private int id; private String username; private Date birthday; private String sex; private String address; // getter/setter方法 }
<!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User"> select * from user where id = #{id} </select> <!-- 根据名称模糊查询用户列表 --> <select id="findUserByUsername" parameterType="java.lang.String" resultType="com.kkb.mybatis.po.User"> select * from user where username like '%${value}%' </select>
- parameterType:定义输入参数的Java类型, - resultType:定义结果映射类型。 - #{}:相当于JDBC中的?占位符 - #{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。 - ${value}:取出参数名为value的值。将${value}占位符替换。 注意:如果是取简单数量类型的参数,括号中的参数名称必须为value
public interface UserDao { public User findUserById(int id) throws Exception; public List<User> findUsersByName(String name) throws Exception; }
public class UserDaoImpl implements UserDao { //注入SqlSessionFactory public UserDaoImpl(SqlSessionFactory sqlSessionFactory){ this. sqlSessionFactory = sqlSessionFactory; } private SqlSessionFactory sqlSessionFactory; @Override public User findUserById(int id) throws Exception { SqlSession session = sqlSessionFactory.openSession(); User user = null; try { //通过sqlsession调用selectOne方法获取一条结果集 //参数1:指定定义的statement的id,参数2:指定向statement中传递的参数 user = session.selectOne("test.findUserById", id); System.out.println(user); } finally{ session.close(); } return user; } @Override public List<User> findUsersByName(String name) throws Exception { SqlSession session = sqlSessionFactory.openSession(); List<User> users = null; try { users = session.selectList("test.findUsersByName", name); System.out.println(users); } finally{ session.close(); } return users; } }
public class MybatisTest { private SqlSessionFactory sqlSessionFactory; @Before public void init() throws Exception { SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); sqlSessionFactory = sessionFactoryBuilder.build(inputStream); } @Test public void testFindUserById() { UserDao userDao = new UserDaoImpl(sqlSessionFactory); User user = userDao.findUserById(22); System.out.println(user); } @Test public void testFindUsersByName() { UserDao userDao = new UserDaoImpl(sqlSessionFactory); List<User> users = userDao.findUsersByName("老郭"); System.out.println(users); } }
#{} :相当于JDBC SQL语句中的占位符? (PreparedStatement) ${} : 相当于JDBC SQL语句中的连接符合 + (Statement)
#{} : 进行输入映射的时候,会对参数进行类型解析(如果是String类型,那么SQL语句会自动加上’’) ${} :进行输入映射的时候,将参数原样输出到SQL语句中
#{} : 如果进行简单类型(String、Date、8种基本类型的包装类)的输入映射时,#{}中参数名称可以任意 ${} : 如果进行简单类型(String、Date、8种基本类型的包装类)的输入映射时,${}中参数名称必须是value
${} :存在SQL注入问题 ,使用OR 1=1 关键字将查询条件忽略
<!-- 添加用户 --> <insert id="insertUser" parameterType="com.kkb.mybatis.po.User"> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
public interface UserDao { public void insertUser(User user) throws Exception; }
public class UserDaoImpl implements UserDao { //注入SqlSessionFactory public UserDaoImpl(SqlSessionFactory sqlSessionFactory){ this. sqlSessionFactory = sqlSessionFactory; } private SqlSessionFactory sqlSessionFactory; @Override Public void insertUser(User user) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try { sqlSession.insert("test.insertUser", user); sqlSession.commit(); } finally{ session.close(); } } }
@Override Public void insertUser(User user) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); try { sqlSession.insert("insertUser", user); sqlSession.commit(); } finally{ session.close(); } }
<insert id="insertUser" parameterType="com.kkb.mybatis.po.User"> <!-- selectKey将主键返回,需要再返回 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}); </insert>
添加selectKey标签实现主键返回。
对象导航图语言
|---User(参数值对象)
|--username--张三
|--birthday
|--sex--男
|-- dept -- Department
|-- name
|--no
OGNL表达式去获取Department对象的name属性:dept.name
此处使用的是JDK的动态代理方式,延迟加载使用的cglib动态代理方式
代理分为静态代理和动态代理。此处先不说静态代理,因为Mybatis中使用的代理方式是动态代理。
动态代理分为两种方式:
开发方式
只需要开发Mapper接口(dao接口)和Mapper映射文件,不需要编写实现类。
开发规范
Mapper接口开发方式需要遵循以下规范:
1、 Mapper接口的类路径与Mapper.xml文件中的namespace相同。
2、 Mapper接口方法名称和Mapper.xml中定义的每个statement的id相同。
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。
4、 Mapper接口方法的返回值类型和mapper.xml中定义的每个sql的resultType的类型相同。
<?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.kkb.mybatis.mapper.UserMapper"> <!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User"> select * from user where id = #{id} </select> </mapper>
/** * 用户管理mapper */ public interface UserMapper { //根据用户id查询用户信息 public User findUserById(int id) throws Exception; }
<!-- 加载映射文件 --> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers>
public class UserMapperTest{ private SqlSessionFactory sqlSessionFactory; @Before public void setUp() throws Exception { //mybatis配置文件 String resource = "SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //使用SqlSessionFactoryBuilder创建sessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindUserById() throws Exception { //获取session SqlSession session = sqlSessionFactory.openSession(); //获取mapper接口的代理对象 UserMapper userMapper = session.getMapper(UserMapper.class); //调用代理对象方法 User user = userMapper.findUserById(1); System.out.println(user); //关闭session session.close(); } }
开发方式
只需要编写mapper接口文件接口。
public interface AnnotationUserMapper { // 查询 @Select("SELECT * FROM user WHERE id = #{id}") public User findUserById(int id); // 模糊查询用户列表 @Select("SELECT * FROM user WHERE username LIKE '%${value}%'") public List<User> findUserList(String username); // 添加并实现主键返回 @Insert("INSERT INTO user (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})") @SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", resultType = int.class, before = false) public void insertUser(User user); }
public class AnnotationUserMapperTest { private SqlSessionFactory sqlSessionFactory; /** * @Before注解的方法会在@Test注解的方法之前执行 * * @throws Exception */ @Before public void init() throws Exception { // 指定全局配置文件路径 String resource = "SqlMapConfig.xml"; // 加载资源文件(全局配置文件和映射文件) InputStream inputStream = Resources.getResourceAsStream(resource); // 还有构建者模式,去创建SqlSessionFactory对象 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindUserById() { SqlSession sqlSession = sqlSessionFactory.openSession(); AnnotationUserMapper userMapper = sqlSession.getMapper(AnnotationUserMapper.class); User user = userMapper.findUserById(1); System.out.println(user); } @Test public void testFindUserList() { SqlSession sqlSession = sqlSessionFactory.openSession(); AnnotationUserMapper userMapper = sqlSession.getMapper(AnnotationUserMapper.class); List<User> list = userMapper.findUserList("老郭"); System.out.println(list); } @Test public void testInsertUser() { SqlSession sqlSession = sqlSessionFactory.openSession(); AnnotationUserMapper userMapper = sqlSession.getMapper(AnnotationUserMapper.class); User user = new User(); user.setUsername("开课吧-2"); user.setSex("1"); user.setAddress("致真大厦"); userMapper.insertUser(user); System.out.println(user.getId()); } }
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandlers(类型处理器)--Java类型--JDBC类型--->数据库类型转换 objectFactory(对象工厂) plugins(插件)--可以在Mybatis执行SQL语句的流程中,横叉一脚去实现一些功能增强,比如PageHelper分页插件,就是第三方实现的一个插件 environments(环境集合属性对象) environment(环境子属性对象) transactionManager(事务管理) dataSource(数据源) mappers(映射器)
SqlMapConfig.xml可以引用java属性文件中的配置信息。
1、在classpath下定义db.properties文件,
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8 jdbc.username=root jdbc.password=root
2、在SqlMapConfig.xml文件中,引用db.properties中的属性,具体如下:
<properties resource="db.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
properties标签除了可以使用resource属性,引用properties文件中的属性。还可以在properties标签内定义property子标签来定义属性和属性值,具体如下:
<properties> <property name="driver" value="com.mysql.jdbc.Driver"/> </properties>
别名的作用:就是为了简化映射文件中parameterType和ResultType中的POJO类型名称编写。
别名 | 映射的类型 |
---|---|
_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 |
map | Map |
在SqlMapConfig.xml中进行如下配置:
<typeAliases> <!-- 单个别名定义 --> <typeAlias alias="user" type="com.kkb.mybatis.po.User"/> <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> <package name="com.kkb.mybatis.po"/> </typeAliases>
使用相对于类路径的资源
如:
<mapper resource="sqlmap/User.xml" />
使用绝对路径加载资源
如:
<mapper url="file://d:/sqlmap/User.xml" />
使用mapper接口类路径,加载映射文件。
如:
<mapper class="com.kkb.mybatis.mapper.UserMapper"/>
注册指定包下的所有mapper接口,来加载映射文件。
如:
<package name="com.kkb.mybatis.mapper"/>
parameterType属性可以映射的输入参数Java类型有: 简单类型、POJO类型、Map类型、List类型(数组) 。
参考入门案例中用户查询的案例。
参考入门案例中的添加用户的案例。
包装对象:pojo类中嵌套pojo。
通过包装POJO传递参数,完成用户查询。
定义包装对象QueryVO
public class QueryVO { private User user; }
SELECT * FROM user where username like '%小明%'
<!-- 使用包装类型查询用户 使用ognl从对象中取属性值,如果是包装对象可以使用.操作符来取内容部的属性 --> <select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user where username like '%${user.username}%' </select>
/** * 用户管理mapper */ public interface UserMapper { //综合查询用户列表 public List<User> findUserList(QueryVo queryVo)throws Exception; }
在UserMapperTest测试类中,添加以下测试代码:
@Test public void testFindUserList() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); //获得mapper的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //创建QueryVo对象 QueryVo queryVo = new QueryVo(); //创建user对象 User user = new User(); user.setUsername("小明"); queryVo.setUser(user); //根据queryvo查询用户 List<User> list = userMapper.findUserList(queryVo); System.out.println(list); sqlSession.close(); }
resultType属性可以映射的java类型有: 简单类型、POJO类型、Map类型 。
不过Map类型和POJO类型的使用情况类型,所以只需讲解POJO类型即可。
使用resultType进行输出映射时,要求sql语句中 查询的列名 和要映射的 pojo的属性名 一致。
查询用户记录总数。
<!-- 获取用户列表总数 --> <select id="findUserCount" resultType="int"> select count(1) from user </select>
//查询用户总数 public int findUserCount() throws Exception;
在UserMapperTest测试类中,添加以下测试代码:
@Test public void testFindUserCount() throws Exception { SqlSession sqlSession = sessionFactory.openSession(); //获得mapper的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int count = userMapper.findUserCount(queryVo); System.out.println(count); sqlSession.close(); }
参考入门程序之根据用户ID查询用户信息和根据名称模糊查询用户列表的案例
如果sql查询列名和pojo的属性名可以不一致,通过resultMap将列名和属性名作一个对应关系,最终将查询结果映射到指定的pojo对象中。
将以下sql的查询结果进行映射:
SELECT id id_,username username_,birthday birthday_ FROM user
// resultMap入门 public List<User> findUserListResultMap() throws Exception;
由于sql查询列名和User类属性名不一致,所以不能使用resultType进行结构映射。
需要定义一个resultMap将sql查询列名和User类的属性名对应起来,完成结果映射。
<!-- 定义resultMap:将查询的列名和映射的pojo的属性名做一个对应关系 --> <!-- type:指定查询结果要映射的pojo的类型 id:指定resultMap的唯一标示 --> <resultMap type="user" id="userListResultMap"> <!-- id标签:映射查询结果的唯一列(主键列) column:查询sql的列名 property:映射结果的属性名 --> <id column="id_" property="id"/> <!-- result标签:映射查询结果的普通列 --> <result column="username_" property="username"/> <result column="birthday_" property="birthday"/> </resultMap> <!-- resultMap入门 --> <select id="findUserListResultMap" resultMap="userListResultMap"> SELECT id id_,username username_,birthday birthday_ FROM user </select>
<id/>:表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发,关联查询用户信息为一对一查询。如果从用户信息出发,查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
查询所有订单信息,关联查询下单用户信息。
SELECT orders.*, user.username, user.address FROM orders LEFT JOIN user ON orders.user_id = user.id
学生们自己实现。
使用resultMap进行结果映射,定义专门的resultMap用于映射一对一查询结果。
创建OrdersExt类( 该类用于结果集封装 ),加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。
public class OrdersExt extends Orders { private User user;// 用户对象 // get/set。。。。 }
在UserMapper.xml中,添加以下代码:
<!-- 查询订单关联用户信息使用resultmap --> <resultMap type="OrdersExt" id="ordersAndUserRstMap"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 一对一关联映射 --> <!-- property:Orders对象的user属性 javaType:user属性对应 的类型 --> <association property="user" javaType="com.kkb.mybatis.po.User"> <!-- column:user表的主键对应的列 property:user对象中id属性--> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="address" property="address"/> </association> </resultMap> <select id="findOrdersAndUserRstMap" resultMap="ordersAndUserRstMap"> SELECT o.id, o.user_id, o.number, o.createtime, o.note, u.username, u.address FROM orders o JOIN `user` u ON u.id = o.user_id </select>
association:表示进行一对一关联查询映射
property:表示关联查询的结果存储在com.kkb.mybatis.po.Orders的user属性中
javaType:表示关联查询的映射结果类型
在UserMapper接口中,添加以下接口方法:
public List<OrdersExt> findOrdersAndUserRstMap() throws Exception;
在UserMapperTest测试类中,添加测试代码:
public void testfindOrdersAndUserRstMap()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //查询订单信息 List<OrdersExt> list = userMapper.findOrdersAndUserRstMap(); System.out.println(list); //关闭session session.close(); }
使用resultMap进行结果映射时,具体是使用association完成关联查询的映射,将关联查询信息映射到pojo对象中。
查询所有用户信息及用户关联的订单信息。
SELECT u.*, o.id oid, o.number, o.createtime, o.note FROM `user` u LEFT JOIN orders o ON u.id = o.user_id
在一对多关联查询时,只能使用resultMap进行结果映射。
1、一对多关联查询时,sql查询结果有多条,而映射对象是一个。
2、resultType完成结果映射的方式的一条记录映射一个对象。
3、resultMap完成结果映射的方式是以[主信息]为主对象,[从信息]映射为集合或者对象,然后封装到主对象中。
在User类中加入List<Orders> orders属性
在UserMapper.xml文件中,添加以下代码:
<resultMap type="user" id="userAndOrderRstMap"> <!-- 用户信息映射 --> <id property="id" column="id"/> <result property="username" column="username"/> <result property="birthday" column="birthday"/> <result property="sex" column="sex"/> <result property="address" column="address"/> <!-- 一对多关联映射 --> <collection property="orders" ofType="orders"> <id property="id" column="oid"/> <result property="userId" column="id"/> <result property="number" column="number"/> <result property="createtime" column="createtime"/> <result property="note" column="note"/> </collection> </resultMap> <select id="findUserAndOrderRstMap" resultMap="userAndOrderRstMap"> SELECT u.*, o.id oid, o.number, o.createtime, o.note FROM `user` u LEFT JOIN orders o ON u.id = o.user_id </select>
Collection标签:定义了一对多关联的结果映射。
property= "orders" : 关联查询的结果集存储在User对象的上哪个属性。
ofType= "orders" : 指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。
// resultMap入门 public List<User> findUserAndOrdersRstMap() throws Exception;
@Test public void testFindUserAndOrdersRstMap() { SqlSession session = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); List<User> result = userMapper.findUserAndOrdersRstMap(); for (User user : result) { System.out.println(user); } session.close(); }
MyBatis根据对关联对象查询的select语句的 执行时机 ,分为三种类型: 直接加载、侵入式加载与深度延迟加载
查询订单信息及它的下单用户信息。
通过对全局参数:lazyLoadingEnabled进行设置,默认就是false。
<settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="false"/> </settings>
<settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 侵入式延迟加载开关 --> <setting name="aggressiveLazyLoading" value="true"/> </settings>
<settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 侵入式延迟加载开关 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
动态SQL的思想:就是使用不同的动态SQL标签去完成字符串的拼接处理、循环判断。
解决的问题是:
1、 在映射文件中,会编写很多有重叠部分的SQL语句,比如SELECT语句和WHERE语句等这些重叠语句,该如何处理
2、SQL语句中的where条件有多个,但是页面只传递过来一个条件参数,此时会发生问题。
综合查询的案例中,查询条件是由页面传入,页面中的查询条件可能输入用户名称,也可能不输入用户名称。
<select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user where 1=1 <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </select>
上边的sql中的1=1,虽然可以保证sql语句的完整性:但是存在性能问题。Mybatis提供where标签解决该问题。
代码修改如下:
<select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user <!-- where标签会处理它后面的第一个and --> <where> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </where> </select>
在映射文件中可使用sql标签将重复的sql提取出来,然后使用include标签引用即可,最终达到sql重用的目的,具体实现如下:
原映射文件中的代码:
<select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user <!-- where标签会处理它后面的第一个and --> <where> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </where> </select>
<sql id="query_user_where"> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> </sql>
<!-- 使用包装类型查询用户 使用ognl从对象中取属性值,如果是包装对象可以使用.操作符来取内容部的属性 --> <select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user <!-- where标签会处理它后面的第一个and --> <where> <include refid="query_user_where"></include> </where> </select>
<include refid="namespace.sql片段”/>
综合查询时,传入多个id查询用户信息,用下边两个sql实现:
SELECT * FROM USER WHERE username LIKE '%老郭%' AND (id =1 OR id =10 OR id=16) SELECT * FROM USER WHERE username LIKE '%老郭%' AND id IN (1,10,16)
在pojo中定义list属性ids存储多个用户id,并添加getter/setter方法
<sql id="query_user_where"> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username like '%${user.username}%' </if> </if> <if test="ids != null and ids.size() > 0"> <!-- collection:指定输入的集合参数的参数名称 --> <!-- item:声明集合参数中的元素变量名 --> <!-- open:集合遍历时,需要拼接到遍历sql语句的前面 --> <!-- close:集合遍历时,需要拼接到遍历sql语句的后面 --> <!-- separator:集合遍历时,需要拼接到遍历sql语句之间的分隔符号 --> <foreach collection="ids" item="id" open=" AND id IN ( " close=" ) " separator=","> #{id} </foreach> </if> </sql>
在UserMapperTest测试代码中,修改testFindUserList方法,如下:
@Test public void testFindUserList() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); // 获得mapper的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 创建QueryVo对象 QueryVo queryVo = new QueryVo(); // 创建user对象 User user = new User(); user.setUsername("老郭"); queryVo.setUser(user); List<Integer> ids = new ArrayList<Integer>(); ids.add(1);// 查询id为1的用户 ids.add(10); // 查询id为10的用户 queryVo.setIds(ids); // 根据queryvo查询用户 List<User> list = userMapper.findUserList(queryVo); System.out.println(list); sqlSession.close(); }
编写批量删除的select标签,parameterType指定为list
注意:foreach标签应该怎么写?
Mybatis提供 查询缓存 ,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
Mybatis的查询 缓存总共有两级 ,我们称之为一级缓存和二级缓存,如图:
@Test public void testOneLevelCache() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper.findUserById(1); System.out.println(user1); // 第二次查询ID为1的用户 User user2 = mapper.findUserById(1); System.out.println(user2); sqlSession.close(); }
@Test public void testOneLevelCache() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper.findUserById(1); System.out.println(user1); User user = new User(); user.setUsername("隔壁老詹1"); user.setAddress("洛杉矶湖人"); //执行增删改操作,清空缓存 mapper.insertUser(user); // 第二次查询ID为1的用户 User user2 = mapper.findUserById(1); System.out.println(user2); sqlSession.close(); }
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用:
service{ //开始执行时,开启事务,创建SqlSession对象 //第一次调用mapper的方法findUserById(1) //第二次调用mapper的方法findUserById(1),从一级缓存中取数据 //方法结束,sqlSession关闭 }
如果是执行两次service调用查询相同 的用户信息,是不走一级缓存的,因为mapper方法结束,sqlSession就关闭,一级缓存就清空。
二级缓存是mapper(namespace)级别的。
下图是多个sqlSession请求UserMapper的二级缓存图解。
<!-- 开启二级缓存总开关 --> <settings> <setting name="cacheEnabled" value="true"/> </settings>
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache --> <cache></cache>
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,比如说存储到文件系统中,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
@Test public void testTwoLevelCache() { 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 = sqlSession3.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper1.findUserById(1); System.out.println(user1); // 关闭SqlSession1 sqlSession1.close(); // 第二次查询ID为1的用户 User user2 = mapper2.findUserById(1); System.out.println(user2); // 关闭SqlSession2 sqlSession2.close(); }
Cache Hit Radio : 缓存命中率
第一次缓存中没有记录,则命中率0.0;
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中)
@Test public void testTwoLevelCache() { 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 = sqlSession3.getMapper(UserMapper.class); // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 User user1 = mapper1.findUserById(1); System.out.println(user1); // 关闭SqlSession1 sqlSession1.close(); //修改查询出来的user1对象,作为插入语句的参数 user1.setUsername("隔壁老詹1"); user1.setAddress("洛杉矶湖人"); mapper3.insertUser(user1); // 提交事务 sqlSession3.commit(); // 关闭SqlSession3 sqlSession3.close(); // 第二次查询ID为1的用户 User user2 = mapper2.findUserById(1); System.out.println(user2); // 关闭SqlSession2 sqlSession2.close(); }
默认二级缓存的粒度是Mapper级别的,但是如果在同一个Mapper文件中某个查询不想使用二级缓存的话,就需要对缓存的控制粒度更细。
在select标签中设置 useCache=false ,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询, 默认情况下是true ,即该statement使用二级缓存。
<select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User" useCache="true"> SELECT * FROM user WHERE id = #{id} </select>
* 默认情况下如果是select语句,那么flushCache是false。
* 如果是insert、update、delete语句,那么flushCache是true。
* 如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
* 如果增删改语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache设置如下:
<select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User" useCache="true" flushCache="true"> SELECT * FROM user WHERE id = #{id} </select>
使用场景:
对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
在使用二级缓存的时候,要设置一下 刷新间隔 (cache标签中有一个 flashInterval 属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等, 单位为毫秒 。
场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决方法
此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。
使用官方网站的Mapper自动生成工具mybatis-generator-core-1.3.2来针对单表生成 po 类(Example)和Mapper接口和mapper映射文件
在generatorConfig.xml中配置Mapper生成的详细信息,注意修改以下几点:
1. 修改要生成的数据库表
2. pojo文件所在包路径
3. Mapper所在的包路径
配置文件如下:
<?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/ssm" userId="root" password="root"> </jdbcConnection> <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" userId="yycg" password="yycg"> </jdbcConnection> --> <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- targetProject:生成PO类的位置 --> <javaModelGenerator targetPackage="com.kkb.ms.po" targetProject="./src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> <!-- 从数据库返回的值被清理前后的空格 --> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- targetProject:mapper映射文件生成的位置 --> <sqlMapGenerator targetPackage="com.kkb.ms.mapper" targetProject="./src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> </sqlMapGenerator> <!-- targetPackage:mapper接口生成的位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.kkb.ms.mapper" targetProject="./src"> <!-- enableSubPackages:是否让schema作为包的后缀 --> <property name="enableSubPackages" value="false" /> </javaClientGenerator> <!-- 指定数据库表 --> <table schema="" tableName="user"></table> <table schema="" tableName="order"></table> </context> </generatorConfiguration>
每次执行逆向工程代码之前,先 删除原来已经生成的mapper xml文件再进行生成。
https://github.com/pagehelper...
* 如果你也在用Mybatis,建议尝试该分页插件,这个一定是 最方便 使用的分页插件。
* 目前几乎支持所有的关系型数据库
* 最新版本是5.1.6。
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.6</version> </dependency>
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- config params as the following --> <property name="helperDialect" value="mysql"/> </plugin> </plugins>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- other configuration --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"> <property name="properties"> <!-- config params as the following --> <value> helperDialect=mysql </value> </property> </bean> </array> </property> </bean>
//获取第1页,10条内容,默认查询总数count PageHelper.startPage(1, 10); List<Country> list = countryMapper.selectAll(); //用PageInfo对结果进行包装 PageInfo page = new PageInfo(list); //测试PageInfo全部属性 //PageInfo包含了非常全面的分页属性 assertEquals(1, page.getPageNum()); assertEquals(10, page.getPageSize()); assertEquals(1, page.getStartRow()); assertEquals(10, page.getEndRow()); assertEquals(183, page.getTotal()); assertEquals(19, page.getPages()); assertEquals(1, page.getFirstPage()); assertEquals(8, page.getLastPage()); assertEquals(true, page.isFirstPage()); assertEquals(false, page.isLastPage()); assertEquals(false, page.isHasPreviousPage()); assertEquals(true, page.isHasNextPage());
Mybatis架构分析及手写Mybatis框架。