笔记8:mybatis
mybatis笔记
--框架
软件开发中的一套解决方案,不同的框架解决不同的问题
好处:框架封装很多细节,使开发者可以使用极简的方式实现功能,大大提高开发效率
--三层框架
【表现层】 用于展示数据
【业务层】 处理业务需求
【持久层】 数据库交互
--持久层技术解决方案
【JDBC技术】 Connection PreparedStatement ResultSet
【Spring的JdbcTemplate】 spring对jdbc的简单封装
【Apache的DBUtils】 和JdbcTemplate很像,是对Jdbc的简单封装
以上都不是框架 JDBC是规范 JdbcTemplate和DBUtils都只是工具类
一、概述
mybatis是持久层框架,java写的
封装jdbc操作很多细节
开发者只需关注sql语句本身无需关注注册驱动,创建连接等繁杂过程
【ORM】 Object Relational Mappging 对象关系映射
把数据库表和实体类及实体类属性对应,操作实体 类就实现操作数据库表
需做到:实体类中属性和数据库表字段名称操持一致
二、入门
【环境搭建】
创建maven工程并导包
mybatis-3.4.5.jar
mysql-connector-java-5.1.6.jar
log4j-1.2.12.jar
创建对应实体类以及dao接口
创建并配置mybatis主配置文件
<?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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql环境 -->
<environment id="mysql">
<!-- 配置事务类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jfs_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="com/xiaoai/dao/IUserDao.xml"/>
</mappers>
创建对应接口映射配置文件
<?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.xiaoai.dao.IUserDao">
<!--
配置查询所有sql
id值:findAll为接口方法名称
-->
<select id="findAll" resultType="com.xiaoai.domain.User">
<!-- 编写sql语句 -->
select * from user
</select>
</mapper>
注意事项:
1--在MyBatista中把持久层的操作接口名称和映射文件叫做:Mapper
所以:IUserDao 和 IUserMapper其实是相同的东西
2--包:com.xiaoai.dao为三级结构
目录:com.xiaoai.dao为一级目录
3--mybatis的映射配置文件位置必须和dao接口的包结构相同
4--映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
5--映射配置文件的操作(select、update等),id取值必须是接口的方法名
resultType为获取数据封装对象类全限定名
*遵从三、四、五点后,在开发中无须再写dao的实现类
【入门案例】
//1--读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2--创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3--使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4--使用SqlSession创建Dao接口代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5--使用代理对象执行方法
List<User> list = userDao.findAll();
for (User user : list) {
System.out.println(user);
}
//6--释放资源
session.close();
is.close();
注意事项:
---不要忘记在映射配置中告知mybatis对应操作结果要封装到哪个实体类中
---配置方式:resultType="com.xiaoai.domain.User"
说明:
1--读取配置文件,绝对路径(d:/xxx/xxx/xx.xml)和相对路径(src/java/xxx/xx.xml等)不用
一般用[类加载器]、[ServletContext对象的getRealPath()],因为当编译后路径可能会改变
2--创建工厂使用了[构建者模式] 优势:把对象的创建细节隐藏,使用者直接调用方法即可拿到对象
3--生产SqlSession使用了工厂模式 优势:解耦(降低类之间的依赖关系)
4--创建Dao接口实现类使用代理模式 优势:不修改源码基础上对已有方法增强
5--实际开发越简便越好,所以不采用dao实现类方式,但mbatis支持写dao实现类
【注解配置】
---接口方法上加对应注解 如:
@Select("select * from user")
public List<User> findAll();
---使用注解配置,不用定义对应接口映射配置文件。
在mybatis主配置文件中<mapper resource="com/xiaoai/dao/IUserDao.xml"/>
改为<mapper class="com.xiaoai.dao.IUserDao"/>
【动态代理】
---proxy.newProxyInstance(类加载器,代理对象要实现的接口字节码数组,如何代理);
--类加载器: 使用和被代理对象相同的类加载器
--代理对象需实现的接口: 和被代理对象实现相同的接口
--如何代理: 是一个InvocationHandler接口,需写一个该接口实现类来实现代理对象接口的方法
三、自定义Mybatis
---创建读取数据流的Resource类读取配置文件
---SqlSessionFactoryBuilder即工厂创建,通过XMLConfigBuilder获取Configuration类获取对应配置信息类并创建SqlSessionFactory即工厂
---创建对应类(XMLConfigBuilder)传递配置文件字节流,
解析配置文件并将获取的配置信息设置到对应配置类(Configuration、Mapper等,其包含数据库、接口等配置化信息)中
---SqlSessionFactory即工厂, 创建SqlSession即创建代理对象的类
---SqlSession即创建代理对象,创建代理对象<T>
---通过代理对象执行相应接口方法
*数据流(配置源文件)--》构建者(配置类)--》工厂(执行类)--》代理类(代理对象)--》执行者(执行方法)
四、获取占位符参数#{} ${}区别
#{}:<select id="findByName" resultType="com.xiaoai.domain.User" parameterType="string">
select * from user where username like #{uid}
</select>select * from user where username like #{uid}
userDao.findByName("%王%")
生成:preparing:select * from user where username like ? 用的是PrepatedStatment的参数占位符sql
${}:<select id="findByName" resultType="com.xiaoai.domain.User" parameterType="string">
select * from user where username like %${value}%
</select>
userDao.findByName("王")
生成:preparing:select * from user where username like %王% 用的是Statment对象的字符串拼接sql
五、配置文件
----主配置文件
【properties标签】 定义属性
<!--
配置properties
可以在标签内部配置连接数据库的信息,也可以通过属性引用外部配置文件信息
---resource属性:
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下
如果是外部文件可用resource引入:
<properties resource="jdbcConfig.properties"></properties>
---url属性:
是要求按照Url的写法来写地址
URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置
写法:http://localhost:8080/mybatisserver/demo1Servlet
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符 它是在应用中可以唯一定位一个资源的
-->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jfs_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<!-- 引用配置的properties -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
【typeAliases标签】 取别名
<!-- 使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<!--
typeAlias用于配置别名,
type属性指定实体类全限定类名。
alias属性指定别名,当指定别名后则不再区分大小写
-->
<!-- <typeAlias type="com.xiaoai.domain.User" alias="user"/> -->
<!--
用于指定要配置别名的包,当指定之后,该包下的实体类都会注册的别名,
并且类名就是别名 ,不区分大小写
-->
<package name="com.xiaoai.domain"/>
</typeAliases>
注意:mappers下的package和上面的有区别:一个指定实体类包,一个指定dao包
<mappers>
<!--
用于指定Dao接口所在的包,当指定了之后就不需要
再写mapper标签resource或者class属性了
-->
<package name="com.xiaoai.dao"/>
</mappers>
----对应接口映射文件
【selectKey标签】
<!--
配置插入操作后,获取插入数据的id 配置在insert标签内
keyProperty ==对应实体类属性名
keyColumn ==对应数据库表字段
resultType ==返回结果类型
order ==在插入语句执行之前还是之后执行
-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
【ResultMap标签】 当实体类对象属性和数据库列表字段不对应时可设置字段的对应关系
<!--
配置 查询结果的列名和实体类的属性名的对应关系
type ==实体类所在全限定名
property ==实体类属性
column ==对应的数据库表字段
-->
<resultMap id="userMap" type="com.xiaoai.domain.User">
<!-- 主键字段的对应 -->
<id property="userId" column="id"/>
<result property="userName" column="username"/>
<result property="userAddress" column="address"/>
<result property="userSex" column="sex"/>
<result property="userBirthday" column="birthday"/>
</resultMap>
<!-- 定义resultMap后还需要在所需配置中引用 如:查询所有用户 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
六、OGNL表达式 (Object Graphic Navigation Language 对象图导航语言)
----它是通过对象的取值方法来获取数据。在写法上把get给省略及后部分首字母变小写
如:获取用户名称 类中==user.getUsername(); ognl==user.username
----在mybatis中为什么能直接写username而不是user.username呢?因为parameterType参数已经指定了实体类
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.xiaoai.domain.User">
update user set username=#{username} where id=#{id}
</update>
----当封装对象的属性是另一对象,而查询的参数在属性对象有,封装对象没有时并且parameterType传递的是封装对象
则可用"."调用链式调用。
public class QueryVo {
private User user;
public User getUser() {return user;}
public void setUser(User user) {this.user = user;}
}
<!-- 根据queryVo的条件查询用户 -->
<select id="findUserByVo" parameterType="com.xiaoai.domain.QueryVo" resultType="com.xiaoai.domain.User">
select * from user where username like #{user.username}
</select>
七、parameterType传入参数类型
----传递简单类型 只包含一个出则可随意定义变量名称
<!-- 根据id查询一个用户 -->
<select id="findById" resultType="com.xiaoai.domain.User" parameterType="int">
select * from user where id=#{uid}
</select>
----传递pojo对象 使用对应获取属性方法去掉get后部分首字母变小写
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.xiaoai.domain.User">
update user set username=#{username} where id=#{id}
</update>
----传递pojo对象的封装对象 使用"."链式调用
<!-- 根据queryVo的条件查询用户 -->
<select id="findUserByVo" parameterType="com.xiaoai.domain.QueryVo" resultType="com.xiaoai.domain.User">
select * from user where username like #{user.username}
</select>
八、resultType输出结果类型
----普通基本类型
----pojo对象
----pojo对象列表
说明:当parameterType传入pojo对象的属性与数据库表字段不一致时的解决方法
----直接通过sql语句修改查询字段别名 如:select id as userId,name as userName from user 等
方便快速,但由于直接在sql语句修改,当多个sql需要时每个sql都要这样定义,后续改造时麻烦
----通过resultMap标签指定pojo属性与表字段对应关系
由于需要多定义一个配置并解析,执行效率慢一些,只需在需要处直接用resultMap="对应配置id"绑定
修改时只修改resultMap配置内容即可,即后续修改方便
九、连接池 实际开发中都会使用连接池 可以减少获取
----mybatis连接池提供3种方式配置
配置位置:主配置文件<dataSource type="POOLED">标签,type属性表示采用何种连接池方式
type属性取值:
【POOLED】 采用传统javax.sql.DataSource规范中的连接池,mybatis中有针对性规范的实现
从池中获取一个连接使用
【UNPOOLED】 采用传统获取连接的方式,虽然也实现javax.sql.DataSource接口,但并没有使用池的思想
创建一个连接使用
【JNDI】 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同服务器所能拿到的DataSource不一样
注意:如果不是web或者Maven的工程,是不能使用的,课程中使用tomcat服务器,采用连接池就是dbcp连接池
十、mybatis中的事务
通过SqlSession对象的commit()和rollback()方法实现事务提交和回滚
开启自动提交:sqlSession = factory.openSession(true);//传入参数true为自动提交事务
十一、动态SQL标签
----【<if>、<where>标签】
<!-- 根据用户信息查询 -->
<select id="findByUser" resultType="user" parameterType="user">
select * from user
<where>
<if test="username!=null and username != '' "><!-- test="表达式" -->
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</where>
</select>
----【<foreach>标签】
<!-- 查询所有用户在 id 的集合之中 -->
<select id="findInIds" resultType="user" parameterType="queryvo">
<!-- select * from user where id in (1,2,3,4,5); -->
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in ( " close=")" item="uid" separator=",">
<!--
collection==待遍历的集合
open==添加语句的开头
close=添加语句的结尾
item=集合中每个元素的临时变量
separator=遍历分隔符
-->
#{uid}
</foreach>
</if>
</where>
</select>
----【<sql>标签】 了解内容,抽取重复sql语句
<!-- 一、抽取重复sql -->
<sql id="defaultUser">
select * from user
</sql>
<!-- 二、引用抽取的sql -->
<select id="findInIds" resultType="user" parameterType="queryvo">
<include refid="defaultUser"></include>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in ( " close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
十二、mybatis中多表查询
表间关系: 一对一 一对多 多对一 多对多
----一对一查询 案例:账户和用户的关系
通过封装一个新类继承父类并加入所需要的属性来当resultType的返回的值完成多表查
public class AccountUser extends Account implements Serializable {
private String username;
private String address;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
@Override
public String toString() {
return super.toString() + " AccountUser [username=" + username + ", address=" + address + "]";
}
}
<!-- 配置查询所有操作-->
<select id="findAll" resultType="com.itheima.domain.AccountUser">
select a.*,u.username,u.address from account a,user u where a.uid =u.id;
</select>
定义封装的account和user的resultMap
<!-- 一、接口对应配置文件建立对应关系 -->
<resultMap type="com.itheima.domain.Account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<association property="user" javaType="com.itheima.domain.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
</association>
</resultMap>
<!-- 二、引用创建的resultMap -->
<select id="findAll" resultMap="accountMap">
select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id;
</select>
//三、获取到信息后打印
System.out.println(account);
System.out.println(account.user);
----多对一、多对多联系的数据都可以通过对应接口配置文件中<association>标签建立对应关系进行查询获得
(注:要在查询对应的实体类中加入泛型为另一所包含信息的实体类的数组)
mybatis中的延迟加载
----延迟加载(按需加载、懒加载): 真正使用数据时才发起查询,不用的时候不查询。 一对多或多对多使用
<resultMap type="com.itheima.domain.Account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!--select属性的内容:查询用户的唯一标识
column属性指定的内容:用户根据id查询时,所需要的参数的值-->
<association property="user" javaType="com.itheima.domain.User" select="com.itheima.dao.IUserDao.findById" column="uid"></association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
<!-- 主配置文件配置参数值 -->
<settings>
<!-- 开启mybatis支持延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 允许触发方法进行立即加载,否则按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
----立即加载: 不管用不用,只要一调用方法,马上发起查询 多对一,一对一使用
mybatis中的缓存
缓存:存在于内存中的临时数据
为什么用缓存:减少数据库交互次数,提高执行效率
什么数据可用,什么不可用:
适用:经常查询、不经常改变、数据正确与否对最终结果影响不大的。
不适用:经常改变、数据正确与否对最终结果影响很大。
----一级缓存:指mybatis中SqlSession对象的缓冲
当我们执行查询后,查询结果会同时存入到SqlSession为为我们提供的一块区域中。
该区域结构是一个Map,当我们再次查询同样数据,会先去SqlSession中查询,有直接用
当SqlSession对象消失时,mybatis一级缓存也就消失了
SqlSession对象.clearCache();//此方法也可清除缓存
当调用SqlSession对象修改、添加、删除、commit()、close()等方法会清空一级缓存
----二级缓存:指mybatis中SqlSessionFactory对象的缓存,
由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
存放的内容是数据,不是对象,拿到数据后再创建对象并填充数据,
虽没有查询数据库但是创建了新对象,所以对象不是同一个
----二级缓存使用步骤:
1--让Mybatis框架支持二级缓存(在主配置文件中配置)
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2--让当前映射文件支持二级缓存(在接口对应配置文件中配置)
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
3--让当前操作支持二级缓存(在select等操作标签中配置)
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
mybatis注解开发
使用注解只是把接口对应的配置文件注解起来,主配置文件内容仍存在
----既有注解也有接口对应配置文件,运行会报错。在同一dao下用了注解就不能再用xml配置文件开发。
> 解决:把接口对应配置文件移动到不相关的目录去
----实体类属性与数据库表字段不一致
> 1-使用别名 如:select username as name from user
> 2-使用注解 即:@Results()完成实体类属性与字段映射
/*查询所有*/
@Select(value = "select * from user")
@Results(id="userMap",value = {//定义id,其他操作可通过@ResultMap()注解引入
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="address",property="userAddress"),
@Result(column="sex",property="userSex"),
@Result(column="birthday",property="userBirthday"),
})
public List<User> findAll();
----------------------------------------例二
//其他操作引入类与字段的映射
@Select("select * from user where id=#{id}")
@ResultMap(value = {"userMap"})
User findById(Integer userid);
----多表查询
通过@Results()注解映射对应信息
----多对一(mybatis中也称一对一)
@Results(id="accountMap",value = {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
//用@Result注解属性one封装对应一的属性(user属性),即通过select字符串找到对应方法及sql语句执行获得对应数据
//fetchType=FetchType.EAGER==什么加载。EAGER==立即加载 LAZY==延时加载
@Result(column="user",property="uid",one=@one(select="com.itheima.dao.IUserDao.findById",fetchType=FetchType.EAGER))
})
----一对多
@Results(id="userMap",value = {//定义id,其他操作可通过@ResultMap()注解引入
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="address",property="userAddress"),
@Result(column="sex",property="userSex"),
@Result(column="birthday",property="userBirthday"),
//封装对应多(Account属性,用many)
@Result(column="accounts",property="id",many=@many(select="com.itheima.dao.IAccountDao.findAccountByUid",fetchType=FetchType.LAZY))
})
----注解开发如何使用二级缓存
----在主配置文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持,不配置也默认开启 -->
<setting name="cacheEnabled" value="true"/>
</settings>
----接口上面通过@CacheNamespace()注解开启二级缓存
@CacheNamespace(blocking=true)