一、Mybatis入门
一、什么是mybatis框架?及其工作原理
-
mybatis支持SQL操作以及高级映射的持久层框架,也被称为ORM框架(mybatis框架、hibernate框架);解决面向对象和关系数据库中数据类型不匹配的问题,通过描述java对象和数据库表之间的映射关系,自动将java应用程序中的对象持久化到关系型数据库的表中;使用ORM框架后,应用程序不在直接访问数据库,而是以面向对象的方式来操作持久化对象,ORM框架会通过映射关系将这些面向对象的操作转化成SQL操作。
-
Hibernate和Mybatis的区别:
- Hibernate是全表映射框架,开发者只需要定义映射关系,不需要掌握SQL语句的编写,开发效率高于Mybatis;但在多表关联时对SQL的支持较差,更新数据时需要发送所有的字段,不支持存储过程,不能通过SQL进行性能优化
- mybatis是半自动映射框架,要手动匹配SQL、POJO和映射关系,支持存储过程,可通过SQL进行性能优化
-
mybatis的工作原理流程图
二.环境搭建
-
创建Maven工程并导入坐标
-
创建实体类和Dao接口
-
创建mybatis的主配置文件(mybatis-config.xml或applicationContext.xml)
-
创建映射配置文件 (mybatis-mapper-config.xml 在mapper源文件夹中)
注意事项:
- mybatis的映射配置文件位置必须和Dao接口的包结构相同
- 映射配置文件的mapper标签namespace属性的取值必须是Dao接口的全限定类名
- 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
- 这样我们不需要再写dao的实现类
三、入门案例
-
基于XML
- mapper配置文件
<?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.liu.dao.UserDao"> <!-- resultType 指定返回到哪个实体类中(domain中)--> <select id="findAll" resultType="com.liu.domain.User"> select * from user </select> </mapper>
- java测试类
public class MybatisTest { @Test public void findAllTest() throws Exception{ //读取配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //创建sqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //使用了构建者模式,将细节隐藏, SqlSessionFactory factory = builder.build(in); //使使用者直接调用方法即可拿到对象 //使用工厂生产sqlSession对象 SqlSession sqlSession = factory.openSession(); //使用了工厂模式,解耦降低类之间的联系 //使用SqlSession创建dao接口的代理对象 UserDao userDao = sqlSession.getMapper(UserDao.class);//使用了代理模式,不修改源码的基础上 //使用代理对象执行方法 //对已有方法的增强 List<User> users = userDao.findAll(); for (User user: users) { System.out.println(user); } //关闭资源 sqlSession.close(); in.close(); } }
注意事项:在映射配置文件中一定要告知封装mybatis在了哪个实体类(全限定类名)中
-
基于注解
在dao接口方法上使用注解,并指定SQL语句
<!--在mybatis-config.xml配置文件中指定实体类的位置--> <mappers> <mapper class="com.liu.dao.UserDao" /> </mappers>
在接口中添加注解信息
public interface UserDao { @Select("select * from user") //查询所有数据 List<User> findAll(); }
四、自定义mybatis框架
- mybatis在使用代理dao的方式实现CRUD
- 创建代理对象
- 在代理对象中调用selectList
二、Mybatis的基本使用
一、mybatis的单表crud操作
-
查询数据
-
创建持久化类User
public class User /*implements Serializable*/ { private int id; private String username; // private Date birthday; private String sex; //private String 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 Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } */ public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } /* public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } */ @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + //", birthday=" + birthday + ", sex='" + sex + '\'' + // ", address='" + address + '\'' + '}'; } }
接口部分就不写了
-
创建映射文件(user-mapper.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.liu.dao.UserDao"> <!-- resultType 指定返回到哪个实体类中(domain中)--> <select id="findAll" resultType="com.liu.domain.User"> select * from user </select> </mapper>
-
创建测试类
public class MybatisTest { @Test public void findAllTest() throws Exception{ //读取配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //创建sqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //使用工厂生产sqlSession对象 SqlSession sqlSession = factory.openSession(); //使用SqlSession创建dao接口的代理对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //使用代理对象执行方法 List<User> users = userDao.findAll(); for (User user: users) { System.out.println(user); } //关闭资源 sqlSession.close(); in.close(); } }
-
-
添加数据
-
创建持久化类(同上)
-
在映射文件中添加新的映射关系
<!-- 插入数据,主键自增 --> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id" parameterType="com.liu.domain.User" > insert into user(username,sex) values (#{username},#{sex}) </insert>
-
测试类测试
//将代理对象执行方法改为insertUser() @Test public void insertUser(){ User user = new User(); user.setUsername("zz"); user.setSex("女"); userDao.insertUser(user); //提交事务 sqlSession.commit(); System.out.println(); }
-
-
删除数据
-
创建持久化类(同上)
-
在映射文件中添加新的映射关系
<!-- 通过id删除数据 --> <delete id="deleteUserById" parameterType="com.liu.domain.User"> delete from user where id = #{id} </delete> <!-- 通过用户名删除数据 --> <delete id="deleteUserByName" parameterType="com.liu.domain.User"> delete from user where username = #{username} </delete>
-
测试类测试
@Test public void deleteUserById(){ User user = new User(); user.setId(3); userDao.deleteUserById(user); sqlSession.commit(); System.out.println(); } @Test public void deleteUserByName(){ User user = new User(); user.setUsername("zz"); userDao.deleteUserByName(user); sqlSession.commit(); System.out.println(); }
-
-
修改数据
-
创建持久化类(同上)
-
在映射文件中添加新的映射关系
<!-- 修改数据 --> <update id="updateUser" parameterType="com.liu.domain.User"> update user set username = #{username}, sex = #{sex} where id = #{id} </update>
-
测试类测试
@Test public void updateUser(){ User user = new User(); user.setId(3); user.setUsername("xw"); user.setSex("男"); userDao.updateUser(user); sqlSession.commit(); System.out.println(); }
-
二、mybatis的核心对象
-
SqlSessionFactory是单个数据库的映射关系经过编译后的内存镜像,用来创建SqlSession;SqlSessionFactory对象的实例通过SqlSessionFactoryBuilder对象来创建;而SqlSessionFactoryBuilder通过XML配置文件或预定好的Configration实例构建出SqlSessionFactory实例;
//读取配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //根据配置文件构建SqlSessionFactory工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSessionFactory是线程安全,整个执行期间都会存在,建议只创建一个,否则创建过多会造成数据库资源浪费
-
SqlSession是应用程序和持久层之间进行交互的单线程对象,负责执行持久化操作;SqlSession对象支持所有SQL操作,可以直接使用实例来操作已经映射的SQL语句;每个线程都应有独立的SqlSession实例,不被共享;线程不安全,应用范围最好在一次请求或方法中,绝不能放在静态字段,使用后及时关闭
三、mybatis的参数和返回值
- mybatis的参数
- parameterType(输入类型)
- 传递简单类型
- 传递pojo类型
- 传递pojo包装对象
- mybatis的返回值
- 输出简单类型
- 输出pojo对象
- 输出pojo列表
四、mybatis配置细节(configuration的几个标签的使用)
-
properties标签:配置属性的元素,将外部的配置动态的替换内部定义的属性;
<!-- 引入数据库连接配置文件 --> <properties resource="db.properties" />
-
settings标签:用于改变mybatis运行时的行为,如开启延时缓存、开启延时加载
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
-
typeAliases标签:为配置文件中java类型设置别名
<typeAliases> <!-- 包较少时,自定义包的别名为user--> <typeAlias type="user" alias="com.liu.domain.User" /> <!-- 包比较多时,自动扫描包来定义别名,默认为实体类对应的包名(基于注解时包名为注解指定的值)--> <package name="com.liu.domain"/> <!-- 不能同时使用--> </typeAliases>
-
typeHandler标签:将预处理语句()中传入的参数从java类型转换为JDBC类型,或从数据库中取出结果时将JDBC类型转化为java类型
<!-- 自定义类型处理器 --> <typeHandlers> <!-- 以单个类的形式配置 --> <typeHandler handler="com.liu.type.handler" /> <!-- 注册一个包中所有的类型处理器,系统会在启动时自动扫描包下的所有文件 --> <package name="com.liu.type"/> </typeHandlers>
-
objectFactory标签:不常用了解即可
-
plugins标签:不常用了解即可
-
environments标签:对环境进行配置,主要是数据源的配置
<!-- 配置环境 --> <environments default="mysql"> <!-- 配置mysql的环境--> <environment id="mysql"> <!-- 配置事务的类型--> <transactionManager type="JDBC"/> <!-- 配置数据源(连接池)--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
-
mappers标签:指定映射文件的位置
-
使用类路径引用
<mappers> <mapper resource="mapper/UserDao.xml"/> </mappers>
-
使用本地文件路径引用
<mapper url="file:///c:/com/liu/mapper/UserDao.xml" />
-
使用接口类引用
<mapper class="com.liu.dao.UserDao" />
-
使用包名引用
<mapper name="com.liu.dao" />
-
六、映射文件(crud部分见上,此处重点讲sql标签和resultMap标签)
-
sql标签:定义可重用的SQL代码片段,在其他语句中引用这些片段
<sql id="UserColumns" >id, username, sex</sql> <select id="findUserById" parameterType="Integer" resultType="com.liu.domain.User"> select <include refid="UserColumns" /> from user where id = #{id} </select>
-
resultMap标签:表示结果映射集,定义映射的规则,级联的更新和定义类型转换器等;解决数据表中的列和对象的属性不完全一致
<!-- resultMap的元素结构 --> <resultMap id="" type=""> <constructor> <!-- 类在实例化是用来注入结果到构造方法中--> <idArg/> <!-- ID参数,标记结果作为ID--> <arg/> <!-- 注入到构造方法的一个普通结果--> </constructor> <id/> <!-- 用于表示哪个列是主键--> <result/> <!-- 注入到字段或JavaBean的普通结果--> <association property=""/> <!-- 用于一对一关联--> <collection property=""/> <!-- 用于一对多关联--> <discriminator javaType=""> <!-- 使用结果值来决定使用哪个结果映射--> <case value=""></case> <!-- 基于某些值的结果映射--> </discriminator> </resultMap>
映射文件的动态SQL
-
动态SQL中的元素
-
元素:相当于if···else语句
<select id="findUserByNameAndSex" resultType="com.liu.domain.User" parameterType="com.liu.domain.User"> select * from user where <if test="username != null and username != ''"> username = #{username} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> </select>
-
( 、) 元素:相当于switch···case···default语句
<select id="findUserByNameOrSex" resultType="com.liu.domain.User" parameterType="com.liu.domain.User"> select * from user where <choose> <when test="username != null and username != ''"> username = #{username} </when> <when test="sex != null and sex != ''"> and sex = #{sex} </when> <otherwise> and birthday is not null </otherwise> </choose> </select>
-
、、元素
<!-- 将where用<where>标签替换,会自动将多余的and或or删除 --> <select id="findUserByNameAndSex" resultType="com.liu.domain.User" parameterType="com.liu.domain.User"> select * from user <where> <if test="username != null and username != ''"> and username = #{username} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> </where> </select> <!-- 将where用<trim>标签替换,prefix属性表示语句的前缀,prefixOverrides表示需要去除的特殊字符串(and) --> <select id="findUserByNameAndSex" resultType="com.liu.domain.User" parameterType="com.liu.domain.User"> select * from user <trim prefix="where" prefixOverrides="and"> <if test="username != null and username != ''"> and username = #{username} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> </trim> </select> <!-- set标签主要用于更新操作,并将最后一个多余的逗号去除 --> <select id="updateUser" parameterType="com.liu.domain.User"> update user <set> <if test="username != null and username != ''"> and username = #{username}, </if> <if test="sex != null and sex != ''"> and sex = #{sex}, </if> where id = #{id} </set> </select>
-
元素
<select id="findUserByIds" parameterType="list" resultType="com.liu.domain.User"> select * from user where id in <foreach item="id" collection="list" index="index" open="(" separator="," close=")" > #{id} </foreach> </select>
item:配置的是循环中当前的元素
index:当前元素在集合中位置的下标
collection:list传过来的参数类型(array、list、collection、Map集合的键、POJO包装类中的数组或集合类型的属性名)
open和close:以什么符号将集合元素包裹起来
separate:各个元素的间隔符
-
元素:模糊查询时实现字符串拼接,${}可能会有SQL注入,因此要用#{},但#{}又只支持mysql;
<select id="findUserByName" parameterType="com.liu.domain.User" resultType="com.liu.domain.User"> <bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'"/> select * from user where username like #{pattern_username} </select>
三、Mybatis的深入和多表
一、mybatis的连接池
- 三种数据源类型:
- UNPOOLED:在每次请求时都会打开和关闭连接,用于简单的操作,如果有较多的连接而不释放资源时不推荐使用,容易造成服务器故障
- POOLED:利用连接池将将JDBC对象组织起来,在建立新连接是避免初始化时间和认证时间,快速响应Web请求;防止在过多的连接中造成服务器故障
- JNDI:在EJB和应用服务器等容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用
二、mybatis的事务管理
-
配置两种类型的事务管理器:如果是SSM框架,则事务交由Spring管理
- JDBC:直接使用JDBC的提交和回滚设置,依赖于从数据源得到的连接来管理事务的作用域
- MANAGED:不主动提交和回滚一个连接,而是有容器管理事务的整个生命周期;默认关闭连接,用closeConnection=“false”来阻止关闭连接
三、mybatis的多表查询(关联映射:一对多、多对一、多对多)
通过标签中的子元素来处理关联关系
-
一对一:
- 创建两个实体类
- 编写配置文件(两个)
- 创建测试类
<!-- 实体类A的Amapper.xml文件 --> <select id="findUserById" parameterType="Integer" resultType="实体类B的一个属性b类型"> select * from where id = #{id} </select> <!-- 实体类B的Bmapper.xml文件 --> <!-- 嵌套查询:通过执行另一条SQL映射语句来返回预期的特殊类型 --> <select id="findUserById" parameterType="Integer" resultMap="map1"> select * from user WHERE id = #{id} </select> <resultMap id="map1" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <!-- 一对一:引入另一条SQL语句--> <association property="card" column="card_id" javaType="实体类B的一个属性b类型" select="mapper.Amapper.findUserById"/> </resultMap> <!-- 嵌套结果:处理重复的联合结果的子集 --> ···········
property:映射到实体类对象属性,和数据表字段一一对应
column:数据表中对应的字段
javaType:映射到实体对象属性的类型
select:指定引入的SQL语句,用于关联映射中的嵌套查询
fetchType:是否启用延迟加载,有lazy(延迟加载)和eager(积极加载)两种,默认lazy延迟加载
-
一对多:使用collection元素来实现一对多的关系,将javaType属性替换成ofType属性(其他同上),两种处理方式:嵌套查询、嵌套结果
-
多对多:使用collection元素来实现多对多的关系,将javaType属性替换成ofType属性(其他同上),两种处理方式:嵌套查询、嵌套结果
四、Mybatis的缓存和注解开发
一、mybatis中的加载时机(查询时机)
- 延迟加载(懒加载):在需要使用数据时才查询,不用时就不查询(加载),适用于一对多和多对多
- 立即加载:不论是否使用数据,一旦调用立即加载(查询),适用于多对一和一对一
二、mybatis的一级缓存和二级缓存
-
缓存模式
- 缓存的作用是将数据保存在内存中,一旦用户查询数据,优先从缓存容器中获取数据,不用频繁的查询数据库,这提高了查询性能;一级缓存是SqlSession的缓存,每个SqlSession类的实体对象都有一个HashMap集合来缓存数据,他们之间互不影响;二级缓存是基于mapper的缓存,跨SqlSession的,多个SqlSession的实例对象共同操作同一个Mapper.xml文件中的SQL语句
- 缓存的作用是将数据保存在内存中,一旦用户查询数据,优先从缓存容器中获取数据,不用频繁的查询数据库,这提高了查询性能;一级缓存是SqlSession的缓存,每个SqlSession类的实体对象都有一个HashMap集合来缓存数据,他们之间互不影响;二级缓存是基于mapper的缓存,跨SqlSession的,多个SqlSession的实例对象共同操作同一个Mapper.xml文件中的SQL语句
-
一级缓存
- 作用域是SqlSession范围内的,在SQL和参数完全一样的情况下,使用同一个SqlSession对象调用同一个mapper方法,只执行一次SQL,数据会放在缓存中,如果没有刷新缓存且缓存没超时,那么就会取出缓存数据,而不和数据库进行交互;如果SqlSession执行了DML操作并提交到数据库,那么就会清空一级缓存(为了保证缓存中是最新信息,避免脏读),基于id的缓存,使用HashMap时,对象的id作为key,对象作为value
- 一级缓存的生命周期
- 如果SqlSession调用了close()方法,会释放一级缓存perpetualChache对象,一级缓存不可用
- 如果SqlSession调用了clearChache()方法,会清空perpetualChache对象中的数据,对象仍然可用
- SqlSession执行了任何DML操作,会清空perpetualChache对象中的数据,对象仍然可用
-
二级缓存
二级缓存概述
-
二级缓存是基于mapper的缓存,跨SqlSession的,多个SqlSession的实例对象共同操作同一个Mapper.xml文件中的SQL语句,当执行了DML操作时,会清空二级缓存;mybatis默认没有开启二级缓存,因此需要我们主动开启;
-
在mybatis的配置文件mybatis-config.xml文件中配置
<!-- 开启二级缓存 --> <settings> <setting name="cacheEnabled" value="true" /> </settings>
-
在mapper文件中配置
<!-- 开启二级缓存 --> <cache />
二级缓存的特点
- 缓存是以namespace为单位的,每个namespace下的操作互不影响
- CRUD会清空二级缓存
cache-ref共享缓存
- 每个mapper分配一个Cache缓存对象(配置)
- 多个mapper共享一个Cache缓存对象(配置)
<!-- mapper1.xml中开启二级缓存 --> <mapper namespace=""> <!-- 必须写,要与和其共享缓存的mapper文件交互 --> <cache /> </mapper> <!-- mapper2.xml中开启二级缓存,并指定共享哪个mapper文件的缓存 --> <mapper namespace=""> <!-- 共享mapper1的二级缓存,要求mapper1.xml中必须有<cache/> --> <cache-ref namespace="" /> </mapper>
-
-
mybatis缓存的工作原理图
三、mybatis的注解开发(单表CRUD、多表查询)
@Select注解的例子
@Select("select * from user")
List<User> findAll();