mybatis框架知识梳理
Mybatis框架概述:
mybatis 通过 简单的xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象(POJO类)和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
为什么使用Mybatis:
传统jdbc访问数据库:
1.未经封装,使用jdbc原始方法(加载驱动,获取连接)进行对数据库数据的crud操作
2.数据库连接频繁创建、释放,造成系统资源的浪费,影响系统性能,
解决:可使用数据库连接池技术
3.Sql语句在代码中属于硬编码,而实际应用sql语句经常变化,需改变java代码,不利于代码维护
解决:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。
4.使用prepareStatement向占位符传参存在硬编码,因为参数不定,条件可多可少,需修改代码不易维护。
解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。
5.结果集解析存在硬编码(查询列名),若sql变化(参数),导致解析代码变化,系统不易维护,可将数据库记录封装为POJO类对象。
解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型
Mybatis入门准备:
环境搭建:
1.创建maven工程,导入依赖坐标。(mybatis,mysql,log4j,junit)
2.编写实体类Usr
3.编写持久层接口UserDao
4.获取log4j.properties到resource目录下
5.编写持久层接口的映射文件UserDao.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">
<!--namespace属性为dao接口的全限定类名-->
<mapper namespace="com.itheima.dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
6.编写SqlMapConfig.xml(Mybatis运行环境的配置信息,dom4j解析xml技术)
<?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/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<!-- 基于xml -->
<mapper resource="com/itheima/dao/IUserDao.xml"/>
<!-- 基于注解 -->
<mapper class="com.itheima.dao.IUserDao.xml"/>
</mappers>
</configuration>
7.编写测试类
public class MybatisTest {
/**
* 入门案例
* @param args
*/
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂(不是自己创建的,构建者模式)
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
}
tips:
1.读取配置文件
第一个:使用类加载器,只能读取类路径的配置文件
第二个:使用servletContext对象的getRealPath()
2.创建工厂mybatis:构建者模式
优势:把对象的创建细节隐藏,使使用者直接调用方法即可拿到对象
3.生产SqlSession:工厂模式
优势:解耦,降低类之间的依赖。使用工厂生产对象,解决类之间的依赖,web开发每次修改改动源码,影响开发效率,使用工厂生产对象,不需要重新编译,部署,启动服务器。
4.创建Dao接口实现类:代理模式
优势:在不修改源码基础上对已有方法进行增强
增强对象的功能:
设计模式:一些通用的解决固定问题的方式
1)装饰模式
2)代理模式
静态代理:有一个类文件描述代理模式
动态代理:在内存中形成代理类
5.prepareStament用法
tips:使用jdbc连接数据库执行sql语句时(执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。)
1.当sql语句含有多个参数,多次使用时,使用preparestament将sql进行初始化,提到数据库中进行预编译,提高效率;
2.可以替换变量,sql语句可以包含占位符?,把?替换成变量,ps.setString()....
3.安全性,有效防止SQL注入的问题:传递给PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。(不太懂)
总结:
PreparedStatement: 数据库会对sql语句进行预编译,下次执行相同的sql语句时,数据库端不会再进行预编译了,而直接用数据库的缓冲区,提高数据访问的效率(但尽量采用使用?号的方式传递参数),如果sql语句只执行一次,以后不再复用。 从安全性上来看,PreparedStatement是通过?来传递参数的,避免了拼sql而出现sql注入的问题,所以安全性较好。
在开发中,推荐使用 PreparedStatement
6.什么是SQL注入,怎么防止SQL注入?
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
怎么防止SQL注入,使用存储过程来执行所有的查询;检查用户输入的合法性;将用户的登录名、密码等数据加密保存。
映射配置文件
crud标签:
1.sql语句使用#{}字符
相当于占位符,里面内容为基本类型时随意写,若参数为对象,则#{}里使用ognl(对象图导航语言)表达式,语法格式为#{对象.对象}的方式
{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写 username。
2.Mysql中获取id
添加记录,使用insert标签。只有参数,没有返回值。
如果Mysql数据想获取当前记录的ID,需要配置:
selectKey
当运行了添加的方法后,mybatis会自动将返回的ID,设置给参数对象parameterType配置的对象
<insert id="saveUser" parameterType="com.itheima.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>
3.模糊查询
我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标
识%。 配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”。
模糊查询另一种查询方式:'%${value}%' 代替#{}
4.{}与${}的区别
#{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类
型值, #{}括号中可以是 value 或其它名称。
${}表示拼接 sql 串
通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简
单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, ${}括号中只能是 value。
Mybatis参数深入:
parameterType:基本类型与string,采用包名.类名,如Java.Lang.String
包装类采用全限定分类名
特殊情况:实体类属性名称与数据库列名不一致,查询结果为null
MySql在windows系统中不区分大小写,映射配置修改如下:
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
改为:
使用别名查询
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select id as userId,username as userName,birthday as userBirthday,
sex as userSex,address as userAddress from user
</select>
思考:
如果我们的查询很多,都使用别名的话写起来岂不是很麻烦,有没有别的解决办法呢? 如下
resultMap结果类型:
<!-- 建立 User 实体和数据库表的对应关系
type 属性:指定实体类的全限定类名
id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
-->
<resultMap type="com.itheima.domain.User" id="userMap">
<id column="id" property="userId"/>
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
<result column="address" property="userAddress"/>
<result column="birthday" property="userBirthday"/>
</resultMap>
id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称
对应映射配置
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
SqlMapConfig.xml配置更新
properties标签:需把jdbcConfig.properties提取出来,放到resources路径下
typeAliases标签:
pakage标签:
<!-- mybatis的主配置文件 -->
<configuration>
<properties resource="jdbcConfig.properties"></properties>
<!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.itheima.domain"></package>
</typeAliases>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<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>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapper以及resource或者class了 -->
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
动态Sql语句
动态SQL目的是为了更好的重用SQL语句的片段或者灵活的生成SQL语句。本质上,就是SQL语句字符串的拼接。
if进行判断,test属性为true,就拼接上标签中的SQL语句
test中就是OGNL表达式
1. 不要加 #{}
2. 逻辑运算,要使用 and,or
3. 调用对象的方法: list.size()
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and sex = #{userSex}
</if>
</where>
</select>
<update id="updateUser" parameterType="user">
update user
<set>
<if test="username !=null and username !=''">
username = #{username} ,
</if>
<if test="username !=null">
sex = #{sex},
</if>
<if test="username !=null">
password = #{password},
</if>
</set>
where id=#{id}
</update>
foreach 进行循环的集合,必须通过包装的对象提供。
<select id="findInIds" resultType="user" parameterType="queryvo">
<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>
将SQL中,相同的内容提取出来,在多个地方进行引用。
1. SQL语句中的字符串
2. 动态SQL中的标签
抽取SQL语句
<sql id="defaultUser">
select * from user
</sql>
使用SQL
<select id="findAll" resultMap="userMap">
<include refid="defaultUser"></include>
</select>
简化sql片段
<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
select * from user
</sql>
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="user">
<include refid="defaultSql"></include>
</select>
一对一查询
使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。
通过面向对象的(has a)关系可以得知,我们可以在 Account 类中加入一个 User 类的对象来代表这个账户
是哪个用户的。
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 一对一的关系映射:配置封装user的内容-->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
一对多查询
<resultMap type="user" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
-->
<collection property="accounts" ofType="account">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
</collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select u.*,a.id as aid ,a.uid,a.money from user u left outer join account
a on u.id =a.uid
</select>
</mapper>
collection
部分定义了用户关联的账户信息。表示关联查询结果集
property="accList":
关联查询的结果集存储在 User 对象的上哪个属性。
ofType="account":
指定关联查询的结果集中的对象类型即 List中的对象类型。此处可以使用别名,也可以使用全限定名。
多对多查询
<!--定义 role 表的 ResultMap-->
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="users" ofType="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="roleMap">
select u.*,r.id as rid,r.role_name,r.role_desc from role r
left outer join user_role ur on r.id = ur.rid
left outer join user u on u.id = ur.uid
</select>
</mapper>
延迟加载
- 什么是延迟加载,为什么要进行延迟加载
- 在加载当前对象的时候,对于对象的关联的属性对象或者属性集合,是否立即查询?不立即查询,就是延迟加载,也叫做懒加载。
- 在mybatis中如何设置延迟加载(association、 collection 具备延迟加载功能)。
- 在当前应用中,开启延迟加载
//SqlMapConfig.xml
<!--配置参数-->
<settings>
<!--开启Mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/> <!-- 默认就是true -->
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
2.一对一
<!-- 一对一的关系映射:配置封装user的内容
select属性指定的内容:查询用户的唯一标识:当前mapper配置的sid(namespace+id)
column属性指定的内容:用户根据id查询时,所需要的参数的值
-->
<association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"></association>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
3.一对多
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
-->
<collection property="accounts" ofType="account"
select="com.itheima.dao.IAccountDao.findByUid"
column="id">
</collection>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
<collection>标签:
主要用于加载关联的集合对象
select 属性:
用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一
个字段名了
缓存
缓存的使用
缓存的作用和缓存使用的时机
- 缓存是为了减少了数据库的交互,提交系统的性能
- 适合使用缓存的情况:
- 经常查询的数据,并且不会经常发生变化。
- 允许数据出现不同步的情况
mybatis的缓存
一级缓存
SqlSession范围的缓存,同一个SQLSession对象,可以使用同一个缓存。当使用session查询相同的数据(同一方法,相同参数),查询出同一个对象。
当session关闭,调用clearCache()方法,执行增删改操作时,会被清空。
二级缓存
SQLSessionFactory范围的缓存,一般应用只会创建一个SQLSessionFactory对象,当前应用中,所有的操作都可以使用二级缓存的数据。不同的session查询相同的数据(同一方法,相同参数)可以从二级缓存中获取,二级缓存只缓存了数据的内容,每个session会拿到不同的对象,拥有相同的属性。
二级缓存默认是不启用的,需要手动开启。
//SqlMapConfig.xml
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
//对应的mapper文件中
<cache />
//当前配置的mapper文件的对应需要缓存的SQL上
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
Mybatis常用注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
搭建开发环境
如果使用了注解开发,就不能再使用xml配置。
单表增删改查操作
对象属性和表字段不一致的配置
@Select("select * from user") @Results(id="userMap",value={ @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") }) List<User> findAll();
//重复使用resultmap @Select("select * from user where id=#{id} ") @ResultMap("userMap") User findById(Integer userId);
4.对象关系映射配置
一对一
@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(property = "user",column = "uid",one=@One(select="com.itheima.dao.IUserDao.findById",fetchType= FetchType.EAGER)) }) //property 对象属性的名称 //column 后面配置的select查询的参数 //one 属性是对象 // select 查询的方法 // fetchType 是否延迟加载 FetchType.EAGER 立即加载 List<Account> findAll();
2.一对多
@Select("select * from user") @Results(id="userMap",value={ @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"), @Result(property = "accounts",column = "id", many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY)) }) // many 对象的属性是集合 fetchType = FetchType.LAZY 延迟就加载 List<User> findAll();