文章目录
Mybatis框架
一,什么是框架
框架是整个或部分系统的可重用设计,表现为一组抽象构件及实例间交互的方法;可被应用开发者定制的应用骨架,封装了很多实现的细节,使开发者可以使用极简的方式实现功能,提高开发效率
二,Mybatis框架概述
mybatis采用orm(Object Relational Mappging 对象关系映射)思想实现了结果集的封装,将数据库表和实体类及其属性对应起来,封装了jdbc操作的很多实现细节,让开发者只需关注SQL语句。
一,快速创建一个mybatis框架:
1,创建maven工程(添加mybatis坐标和数据库驱动的jar的坐标)
2,编写实体类(实现序列化接口)
3,编写持久层接口,在接口里定义操作数据库的方法
4,编写持久层接口的映射文件(.xml文件的名称和位置要与接口相同,id属性的值要与dao接口里面的方法相同)
5,编写mybatis的配置文件
6.编写测试类
如果是使用注解的方式,就不必配置接口的映射文件,将配置文件中mappers里改为class。
二,创建mybatis时涉及的原理和所用的设计模式
1.在第五步创建配置文件时:
<configuration>
<!-- 配置 mybatis 的环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>
解析:
environment可以配置多个,用id值来标识,其中<environments default="mysql">,default这个值可以选择要使用哪个数据库
dataSource这个值的type类型可以选择的值有:POOLED,UNPOOLED,JNDI三种
mapper这个属性标识mapper配置文件的的位置,如果是使用注解,将resource改为class,填入mapper文件的全类名。
2,在测试类中
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
解析:
1,读取配置文件,因为要将项目部署在服务器上运行,所以使用绝对路径(自己电脑上的物理路径)和相对路径(src下的路径)读取文件都会产生错误,所以有两种方法:
使用类加载器(只能读取类路径的配置文件)
InputStream is=Test.class.getClassLoader().getResourceAsStream(str);
使用ServletContext对象的getRealPath()
Reader reader = Resources.getResourceAsReader(resource);
2.创建SqlSessionFactory对象时用了构建者模式,把对象的创建细节隐藏,使用者直接调用方法即可拿到对象,builder.build(in),我们传入参数in,builder就会构建出我们想要的对象
3.factory.openSession():此处使用了工厂模式,降低类之间的依赖关系,创建产品时不用直接new对象,可以直接调用工厂方法生产产品。
4.session.getMapper(接口的字节码文件):此处使用了代理模式,可以在不修改源码的基础上对已有的方法进行增强。
5.userDao.findAll(),此处使用反射,通过Projo类的全限定类名(mapper映射文件中的resultType中的字符串)通过反射创建出对象,用于封装结果集
3,mybatis的入门程序原理分析
三,实现增删改查操作
一,添加测试类:
测试类中必须从读取文件开始到关闭资源总要执行7步,可以将一些对象提取出来,利用junit的注解方法,
@After和@before这两个方法,在每次执行时,先执行@Before的方法,再执行调用crud的方法,再执行@After的方法,进行资源的提交和关闭
二,执行crud操作一些属性的介绍
1,在执行查询操作时
需要写返回值,resultType类型,值为被封装的类的全限定名。
如果根据条件查询,就需要写parameterType,如果是基本数据类型可以不用写,sql语句的查询条件可以用#{}来接收,相当于占位符,执行后就被解析为 ?号
2,在执行保存和修改操作时
parameterType中要写被封装结果集的Projo类对象,在sql语句中
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
#{}要写Projo类中的属性名称,这个是采用ognl 表达式(它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言,它是按照一定的语法格式来获取数据的。语法格式就是使用 #{对象.对象}的方式,#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写 username)
3,如果返回新增用户的id值
<insert id="saveUser" parameterType="USER">
<!-- 配置保存时获取插入的 id --> <selectKey keyColumn="id" keyProperty="id" resultType="int">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
4,模糊查询
有两种方法:
1,sql语句的条件写为#{属性值},那么在查询时传入的参数带%
select * from user where username like #{username}
List<User> users = userDao.findByName("%王%");
sql语句编译后是?
2,sql语句中用${},可以在sql语句中写%,那么在查询时传入的参数就不必写%了。
select * from user where username like '%${value}%'
注意:此处的${value}是固定的,不能改为其他的类型,因为源码中是写死的
List<User> users = userDao.findByName("王");
sql语句编译后是%王%
5,#{}和${}的区别
#{}表示一个占位符,
通过#{}可以实现 preparedStatement 向占位符中设置值自动进行 java 类型和 jdbc 类型转换,可以有效防止 sql 注入。#{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类
型值,#{}括号中可以是 value 或其它名称,名字由自己写
${}表示拼接 sql串
通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简
单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是value。
6,深入解析parameterType参数
paramterType属性可以传递基本类型,引用类型,实体类类型还可以传递包装类类型
1.基本类型和String类型,
我们可以直接写类型名称也可以使用包名.类名的方式,因为mybatis在加载时已经把常用的数据类型注册了别名。
2.实体类类型
只能写全限定类名,因为这个是我们自己定义的,实体类并没有注册别名。
3,projo包装对象
此时的查询条件是综合的查询条件
例:
根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中(Pojo 类中包含 pojo)
1,QueryVo这个类中有User这个属性
2.dao层 List<User> findByVo(QueryVo vo);
3,映射文件
<!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
<select id="findByVo" resultType="com.itheima.domain.User"
parameterType="com.itheima.domain.QueryVo">
select * from user where username like #{user.username};
</select>
4.测试类
@Test
public void testFindByQueryVo() {
QueryVo vo = new QueryVo();
User user = new User();
user.setUserName("%王%");
vo.setUser(user);
List<User> users = userDao.findByVo(vo);
for(User u : users) {
System.out.println(u);
}
}
7,深入理解resultType属性
1.resultType和paramterType属性类似,支持基本类型和实体类型,基本类型可以直接使用名称,实体类必须使用全限定名称
2.当为实体类型时,实体类中的属性名称必须和查询语句中的列名保持一致
如果不一致,结果集就不会封装成功,(MySQL在windows系统中不区分大小写)
可以通过给sql语句的列起别名而让他们一致(比较麻烦)可以通过resultMap标签建立数据库表的列名和实体类的属性名称建立对应关系。
8.深入理解resultMape属性
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询
四,mybatis的配置文件解析
1.配置文件的内容和顺序
-properties(属性)
--property
-settings(全局配置参数)
--setting
-typeAliases(类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
2,Properties(属性)
有两种方式:
1.直接写在配置文件中
<properties>
<property name="jabc.driver" value="com.mysql.jdbc.Driver">
...
</properties>
2,在classpath下定义db.properties文件
在配置文件中引入时有两种方式:
resource属性:
用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
resource=“jdbcConfig.properties”
url属性:
URL: Uniform Resource Locator 统一资源定位符
http://localhost:8080/mystroe/CategoryServlet URL
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符
/mystroe/CategoryServlet
它是可以在 web 应用中唯一定位一个资源的路径
<properties url=
file:///D:/Javaworkspace/mybatis/src/main/resources/jdbcConfig.prop
erties">
</properties>
3,typeAliases(类型别名)
< typeAliases>
<!-- 单个别名定义 -->
< typeAlias alias="user" type="com.itheima.domain.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.itheima.domain"/>
<package name="其它包"/>
< /typeAliases>
4,mapper(映射器)
三种方式:
resource (配置文件读取) class(注解)< package name=""/>(两者都可以)
注意:class和package要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
五,连接池和事务
一,mybatis的连接池技术
1.mybatis连接池的分类:
unpooled(不使用连接池的数据源);pooled(使用连接池的数据源);JNDI(使用JNDI实现的数据源)
相应地,MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,
PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源。
2.数据源的配置和存取
MyBatis 在初始化时,根据< dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即:
type=”POOLED”:MyBatis 会创建 PooledDataSource 实例
type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例
type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用
MyBatis 是 通 过 工 厂 模 式 来 创 建 数 据 源 DataSource 对 象 的 , MyBatis 定 义 了 抽 象 的 工 厂 接口:org.apache.ibatis.datasource.DataSourceFactory,通过其 getDataSource()方法返回数据源
DataSource,创建了DataSource实例后,将其放到Configuration对象内的Environment对象中,供以后使用
3.Mybatis中连接的获取过程分析
执行时间:当我们需要创建SqlSession对象并且需要执行SQL语句时,Mybatis才会去调用dataSource对象来创建connection对象(即connect对象的创建一直延迟到执行sql语句)
在unpooled中调用的方法是doGetConnection方法,里面创建了一个properties对象,将url,username.password等利用set方法存储,返回时又调用doGetConnection方法,在里面这个方法中利用DriverManager获取connection的连接
在pooled中调用的方法是popConnection()方法,对连接池的空闲连接和活动的连接数进行判断,然后得到一个连接,用完之后又释放返回到连接池中
连接池就是用于存储连接的一个容器;容器其实就是一个集合对象,该集合必须是线程安全的,该集合实现队列的特性
源码展示
二,Mybatis的事务控制
mybatis事务提交默认是false,我们需要完成操作后手动提交:session.commit()
从源码中可以看到
public SqlSession openSession(boolean autoCommit){
return openSessionFromDataSource(configuration.getDefaultExecutorType(),null,autoCommit);
}
可以在调用openSession()时,加入参数
如:session.openSession(true),就可以自动提交
六,动态sql
1.< if>标签,多个条件查询
<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</select>
<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法
2,< where>标签
<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<where>
<if test="username!=null and username != '' ">
and username like #{username}
</if> <if test="address != null">
and address like #{address}
</if>
</where>
</select>
3.< foreach>标签
<!-- 查询所有用户在 id 的集合之中 -->
<select id="findInIds" resultType="user" parameterType="queryvo">
<!-- select * from user where id in (1,2,3,4,5); -->
<include refid="defaultSql"></include>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
SQL 语句:
select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
4.简化编写sql片段
<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
select * from user
</sql>
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="user">
<include refid="defaultSql"></include>
</select>
<!-- 根据 id 查询 -->
<select id="findById" resultType="UsEr" parameterType="int">
<include refid="defaultSql"></include>
where id = #{uid}
</select>
七,多表联合查询
表之间的关系分为一对一,一对多和多对多,但是mybatis的联合查询分为两类对一的关系和对多的关系,其中设计到的关键属性是:association(对一)和collection(对多)
一对一和一对多中的对一的关系两种实现方法
1,定义专门的projo类作为输出类型(一对一,可以让一个类继承另一个类),其中定义了sql查询 结果集的所有字段
和单表操作一样,将结果集封装在这个projo类中
2.在一个类中定义另一个projo类的对象的属性,设置get和set方法
在xml文件中利用resultMap建立对应关系,其中association的属性有property(主表的类中定义的从表的属性)JavaType(从表的类的全限定类名)
一对多中对多的关系和多对多的关系
在主表类中定义从表类属性的集合,其中collection的属性中properties(主表的类中定义的从表的集合属性)ofType(集合元素的数据类型)
sql语句一般写为左外或者右外。
八,Mybatis延迟加载策略
延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:当需要数据时才去进行数据库查询,查询工作耗费时间,造成用户等待时间变长,用户体验下降
association和collection具备延迟加载功能
1.使用association实现延迟加载
1.在配置文件中设置延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
2.在原来对一查询的基础上只对mapper文件进行修改
<mapper namespace="com.itheima.dao.IAccountDao">
<!-- 建立对应关系 -->
<resultMap type="account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<association property="user"
javaType="user"
select="findById"
column="uid">
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
<select id="findById" resultType="user" parameterType="int" >
select * from user where id = #{uid}
</select>
</mapper>
这种方法同样可以用于查询对一的关系
2.使用collection延迟加载,其使用与association类似
九,Mybatis缓存
通过缓存策略来减少数据库的查询次数,从而提高性能,Mybatis中缓存分为一级缓存(sqlSession)和二级缓存(namespace)
一,一级缓存
证明一级缓存的存在
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
<mapper namespace=*"com.itheima.dao.IUserDao"*>
<!-- 根据 id 查询 -->
<select id=*"findById"* resultType=*"UsEr"* parameterType=*"int"* useCache=*"true"*>
select * from user where id = #{uid}
</select>
一级缓存清空操作:SqlSession的修改。添加,删除。commit()和close()还有sqlSession.clearCache()的操作
二,二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
<settings>
<!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/>
</settings>
cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为false 代表不开启二级缓存。
二级针对每次查询都需要最新的数据 sql,一般要设置成 useCache=false,禁用二级缓存。
当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象
十,Mybatis注解开发
一,常用的注解说明
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
二,实现基本的增删改查
1.实例:
查询所有用户
@Select("select * from user")
@Results(id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
})
List<User> findAll();
根据id查询一个用户
@Select("select * from user where id = #{uid} ")
@ResultMap("userMap")
User findById(Integer userId);
保存操作
@Insert("insert into
user(username,sex)values(#{username},#{sex}")
@SelectKey(keyColumn="id",keyProperty="id",resultType=Integer.class,before = false, statement = { "select last_insert_id()" })
int saveUser(User user);
2.配置文件
<!-- 配置映射信息 -->
<mappers>
<!-- 配置 dao 接口的位置,它有两种方式
第一种:使用 mapper 标签配置 class 属性
第二种:使用 package 标签,直接指定 dao 接口所在的包-->
<package name="com.itheima.dao"/>
</mappers>
三。使用注解实现复杂关系的映射开发
一,复杂关系映射注解说明
@Results 注解
代替的是标签<resultMap>
该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results({@Result(),@Result()})或@Results(@Result())
@Result 注解
代替了 <id>标签和<result>标签
@Result 中 属性介绍:
id 是否是主键字段
column 数据库的列名
property 需要装配的属性名
one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)()))
@One 注解(一对一)
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
select 指定用来多表查询的 sqlmapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式:
@Result(column=" ",property="",one=@One(select=""))
@Many 注解(多对一)
代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义;
使用格式:
@Result(property="",column="",many=@Many(select=""))
例:加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。
@Select("select * from account")
@Results(id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",property="user",
one=@One(select="com.itheima.dao.IUserDao.findById",
fetchType=FetchType.LAZY)
)
})
List<Account> findAll();
二,使用注解配置二级缓存
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
写在最后,这个笔记是我在网上学习了视频之后的总结,与老师讲的笔记有相似之处
视频链接