大家好,我是IT修真院北京分院java第27期学员,一枚正直纯洁善良的web程序员。
今天给大家分享一下,修真院官网JAVA(职业)任务1的知识点——mybatis常用标签和动态查询。
1.背景介绍
MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以对配置和原生Map使用简单的XML或注解,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
MyBatis是一个ORM框架--(ORM:对象关系映射(Object Relation Mapping)用于实现面向对象编程语言里不同类型系统的数据之间的转换。把表映射成实体类,把表中字段映射实体类属性(POJO)。
常见的ORM开源框架:MyBatis, Hibernate。MyBatis和Hibernate不同,他更加关注原生SQL编写来实现数据库CRUD操作。MyBatis采用功能强大的基于OGNL的表达式来消除其他元素。#{…}
2.知识剖析
2.1 基本知识
SqlSessionFactory:每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得。而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例。
SqlSession:SqlSession完全包含了面向数据库执行SQL命令所需的所有方法。你可以通过SqlSession实例来直接执行已映射的SQL语句。
XML配置文件(configuration XML)中包含了对MyBatis系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)。
作用域和生命周期:
SqlSessionFactoryBuilder一旦创建了SqlSessionFactory,就不再需要它了,因此该实例的最佳作用域是方法作用域,也就是局部方法变量。
SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建,因此SqlSessionFactory的最佳作用域是应用作用域。
SqlSession:每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将SqlSession实例的引用放在一个类的静态域,甚至一个类的实例变量也不行,也绝不能将SqlSession实例的引用放在任何类型的管理作用域中。
2.2 Configuration.xml配置文件
2.2.1 properties标签
包含了mybatis的属性信息。如果属性在多个地方进行配置,mybatis加载顺序为:
--在properties元素体内指定的属性首先被读取。
--然后根据properties元素中的resource属性读取类路径下属性文件或根据url属性指定的路径读取属性文件,并覆盖已读取的同名属性。
--最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。
从MyBatis 3.4.2开始,可以为占位符指定一个默认值,如:${username:ut_user}"。
2.2.2 settings标签
设置mybatis的行为,通常采用默认设置,若有特殊需要,则在文件中显式设置。
2.2.3 typeAliases标签
类型别名是为Java类型设置一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余。可以指定一个包名,MyBatis会在包名下面搜索需要的Java Bean,可以在Bean上使用注解。
2.2.4 typeHandlers标签
无论是MyBatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成Java类型。可以重写类型处理器或创建自定义的类型处理器来处理不支持的或非标准的类型。
2.2.5 objectFactory标签
MyBatis每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。可以创建自定义的对象工厂来覆盖默认行为。
2.2.6 plugins标签
可以在已映射语句执行过程中的某一点进行拦截调用。
2.2.7 transactionManager标签
MyBatis中有两种类型的事务管理器:
JDBC –这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
MANAGED –让容器来管理事务的整个生命周期,比如JEE应用服务器的上下文。如果使用Spring + MyBatis,则没有必要配置事务管理器,Spring模块会使用自带的管理器来覆盖前面的配置。
2.2.8 dataSource标签
dataSource使用标准的JDBC数据源接口来配置JDBC连接对象的资源。有三种类型的数据源:
UNPOOLED—这个数据源的实现只是每次被请求时打开和关闭连接。
POOLED—这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
JNDI—这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。
2.2.9 mappers标签
用来告诉MyBatis去哪里找映射文件,可以通过设置resource、url、class或指定Java bean包名等。
2.3 Mapper.xml映射文件
映射文件有几个顶级元素,按顺序为:
cache:给定命名空间的缓存配置。
cache-ref:其他命名空间缓存配置的引用。
resultMap:用来描述如何从数据库结果集中来加载对象。
sql:可被其他语句引用的可重用语句块。
insert、update、delete、select:分别映射插入、更新、删除、查询语句。
2.4 动态查询标签
if、choose、otherwise、when:用于在查询时有条件地包含where子句的一部分。
trim、where、set:用于处理查询条件语句拼接的情况。
foreach:用于在构建IN条件语句的时候,对一个集合进行遍历。
bind:用于创建一个变量并将其绑定到sql语句上下文中。
以上是一些mybatis常用标签的基本介绍,具体使用见视频。
3. 常见问题
Q: parameterType指的的类型是集合类型还是对象?
A:都可以,甚至不用在xml中指定也可以。第一,mybatis会对传入的参数进行判断是不是list或者array,第二,mybatis是根据ONGL表达式,即 【参数.属性】 这样的格式,通过反射去获取和注入属性值,传入的参数为集合的时候,不管指定parameterType的那一个,上面说的两点都能发挥功能。
Q: resultType和resultMap的区别?
A: 1>resultType 返回的结果类型 <br>
2>resultMap 描述如何将结果集映射到Java对象
Q: #{}和${}的区别是什么?
A:网上的答案是:#{}是预编译处理,${}是字符串替换。mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;mybatis在处理${}时,就是把${}替换成变量的值。使用#{}可以有效的防止SQL注入,提高系统安全性。
对于这个题目我感觉要抓住两点: (1)$符号一般用来当作占位符,常使用Linux脚本的人应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分$和#,从而不容易记错了。 (2)预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。 </section>
Q: 最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
A: (京东面试题) Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id
= findStudentById的MappedStatement。在Mybatis中,每一个“select”,“insert”,“update”,“delete” 标签,都会被解析为一个MappedStatement对象。 Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。 Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
4. 解决方案
如上
5. 编码实战
<?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">
<resultMap id="user" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="qq" column="qq"/>
<result property="job" column="job"/>
<result property="school" column="school"/>
<result property="url" column="url"/>
</resultMap>
<select id="findUserList" resultMap="user">
SELECT * FROM user
</select>
<select id="findUserById" parameterType="_int" resultType="user">
SELECT * FROM user WHERE id = #{id}
</select>
<insert id="addUser" parameterType="user">
INSERT INTO user(username,qq,job,school,url) VALUES (#{username},#{qq},#{job},#{school},#{url})
</insert>
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE id = #{id}
</delete>
<update id="updateUser" parameterType="user">
UPDATE user SET username = #{username} WHERE id = #{id}
</update>
</mapper>
<?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>
<!--setting-->
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载(即按需加载) -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 打开全局缓存开关(二级缓存)默认值就是 true -->
<setting name="cacheEnabled" value="true"/>
</settings>
<!--别名定义-->
<typeAliases>
<package name="com.lihoo.sm.pojo"/>
</typeAliases>
<!-- 加载映射文件 -->
<mappers>
<!-- 通过 resource 方法一次加载一个映射文件 -->
<mapper resource="sqlmap/UserMapper.xml"/>
<!-- 批量加载mapper -->
<!--<package name="com.lihoo.sm.mapper"/>-->
</mappers>
</configuration>
package com.lihoo.sm.test;
import com.lihoo.sm.dao.UserDao;
import com.lihoo.sm.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
/**
* @author lihoo
* @Title: UserServiceTest
* @ProjectName spring_mybatis_1
* @Description: TODO
* @date 2018/8/2-18:41
*/
public class UserServiceTest {
private ApplicationContext applicationContext;
// 在执行测试方法之前首先获取 Spring 配置文件对象
// 注解@Before在执行本类所有测试方法之前先调用这个方法
@Before
public void setup() throws Exception {
applicationContext =
new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void testFindUserList() throws Exception {
//
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
//
List<User> list = userDao.findUserList();
//
for (User userList : list) {
System.out.println(userList);
}
}
@Test
public void testFindUserById() throws Exception {
// 通过配置资源对象获取 userDAO 对象
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// 调用 UserDAO 的方法
User user = userDao.findUserById(2);
//输出用户信息
System.out.println(user.getId() + ":" + user.getUsername() + "," +user.getQq());
}
@Test
public void testAddUser() throws Exception {
// 通过配置资源对象获取 userDAO 对象
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// 调用 UserDAO 的方法
User user = new User();
// user.setId(4);
user.setUsername("VAVA");
user.setQq(8524);
user.setSchool("城市大学");
user.setJob("rapper");
user.setUrl("www.zhihu.com");
int us = userDao.addUser(user);
System.out.println(us);
System.out.println(user.getId() + ":" + user.getUsername() );
}
@Test
public void testDeleteUser() throws Exception {
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
int user = userDao.deleteUser(9);
System.out.println(user);
}
@Test
public void testUpdateUser() throws Exception {
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
User user = new User();
user.setId(9);
user.setUsername("黄旭");
userDao.updateUser(user);
System.out.println(user.getId() + ":" + user.getUsername() );
}
}
// @Test
// public void testFindUserList() throws Exception {
//
// UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// List<User> list = userDao.findUserList();
//
// for (User user : list) {
// System.out.println(user);
// }
//
//
// System.out.println(list.size());
// }
package com.lihoo.sm.dao.Impl;
import com.lihoo.sm.dao.UserDao;
import com.lihoo.sm.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
/**
* @author lihoo
* @Title: UserDaoImpl
* @ProjectName spring_mybatis_1
* @Description: TODO
* @date 2018/8/2-18:35
*/
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public List<User> findUserList() throws Exception {
SqlSession sqlSession = this.getSqlSession();
List<User> list = sqlSession.selectList("test.findUserList");
return list;
}
@Override
public User findUserById(int id) throws Exception {
// 继承 SqlSessionDaoSupport 类,通过 this.getSqlSession() 得到 sqlSession
SqlSession sqlSession = this.getSqlSession();
User user = sqlSession.selectOne("test.findUserById", id);
return user;
}
@Override
public int addUser(User user) throws Exception {
// 继承 SqlSessionDaoSupport 类,通过 this.getSqlSession() 得到 sqlSession
SqlSession sqlSession = this.getSqlSession();
int rs= sqlSession.insert("test.addUser", user);
return rs;
}
@Override
public int deleteUser(int id) throws Exception {
SqlSession sqlSession = this.getSqlSession();
int rs = sqlSession.delete("test.deleteUser", id);
return rs;
}
@Override
public int updateUser(User user) throws Exception {
SqlSession sqlSession = this.getSqlSession();
int rs = sqlSession.update("test.updateUser",user);
return rs;
}
}
// @Override
// public List<User> findUserList() throws Exception {
//
// SqlSession sqlSession = this.getSqlSession();
// List<User> list = sqlSession.selectList("test.findUserList");
// return list;
// }
6. 扩展思考
Mybatis与JDBCTemplate
Mybatis可自定义多种操作,在ObjectFactory中,在TypeHandler中,在Plugin中。这样方便排查在整个操作过程中可能出现的问题,但是需要写许多实现代码。
JDBCTemplate使用简单,也可以自行处理一些操作异常,相比之下实现代码较少。
7. 参考文献
参考:Mybatis中文文档
8. 更多讨论
Mybatis实现更复杂的操作,如嵌套查询、联表查询等
问:为什么一开始运行,plugin类中的方法会直接打印出结果?
答:因为在config文件中,plugin元素设置了property属性,实例化时则会首先执行setProperties()方法。
问:为什么在demo中执行了更新和插入语句,获取结果是已经更新的结果,但是用navicat和cmd查看mysql的数据却没有更新?
答:这个问题自己也没搞清楚,在config文件中已经把缓存设置关掉了,按道理如果操作正常,mysql应该会执行更新,实际结果却不是这样。而且当删除更新和插入操作,单独执行获取数据,返回的结果是mysql中原来的数据,这说明更新和插入操作确实没有执行到mysql中。
问:会不会是plugin类拦截了update方法?
答:不是,将plugin类从config文件中注释掉执行,还是同样的结果。
鸣谢
感谢大家观看
------------------------------------------------------------------------------------------------------------------------
今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~