概述
持久层技术的底层解决方案:
JDBC技术:Connection,PreParedStatement,ResultSet
JDBC简单回顾:
Class.forName("com.mysql.jdbc.Driver") // 加载驱动
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); // 获取连接
Statement stmt = conn.createStatement(); // 获取statement
PreparedStatement ptmt = conn.preparedStatement(sql); // 预编译sql语句
ResultSet rs1 = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess"); // 执行sql语句
ResultSet rs2 = ptmt.execute();
rs.getString(itemName); // 获取结果
rs.getInt(itemName);
rs.next();
mybatis是一个Java语言的持久层框架,封装了JDBC的细节,使开发者只需关注SQL语句本身,无需关心注册驱动,创建连接,创建statement等繁琐的过程。
并使用ORM思想实现了结果集的封装。
ORM:
object relational mapping 对象关系映射
ORM就是把数据表和实体类以及实体类中的属性对应起来,让使用者可以通过操作实体类实现操作数据表。
入门案例
工程搭建:
- 创建maven工程并导入相关依赖
- 创建实体类和持久层接口
- 创建mybatis的主配置文件,在xml配置文件中完成环境配置。
- 创建映射配置文件。
映射配置文件位置必须和dao接口的包名结构相同。
映射配置文件中mapper标签中的namespace属性,必须是dao接口的全限定类名。
映射文件中的操作配置,id属性的取值必须是dao接口中的方法名,resultType属性写名返回值的封装类型
<mapper namespace="com.meituan.domain.User"> <!-- 对应的实体类的全限定类名 -->
<select id="findAll" resultType="com.meituan.domain.User"> <!-- 接口中的方法名,返回值封装类型 -->
select * from user;
</select>
<insert id="saveUser" parameterType="com.meituan.domain.User">
insert into user(username, sex, age, address, birthday) value(#{username}, #{sex}, #{age}, #{address}, #{birthday});
<!-- 在出入语句执行之后获取插入id,相当于在执行插入操作之后再执行一条语句 -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="after">
select last_insert_id();
</selectKey>
</insert>
<update id="updateUser" parameterType="com.meituan.domain.User">
update user set (username=#{username}, sex=#{sex}, age=#{age}, address=#{address}, birthday=#{birthday}) where id=#{id};
</update>
<delete id="deleteUser" parameterType="java.lang.Integer"> <!-- 此处的输入类型可以写java.lang.Integer或int -->
delete from user where id=#{uid};
</delete>
<!-- 在使用模糊查询时,在调用方法时添加%,这种方法更常用,因为利用到了预处理preparedStatement,效率更高 -->
<!-- List<User> users = userDao.findByName("%王%"); -->
<select id="findByName" parameterType="string" resultType="com.meituan.domain.User">
select * from user where username like #{name};
select * from user where username like '${name}'; <!-- 此时模糊查询时不需要% -->
</select>
<select id="count" resultType="int">
select count(id) from user;
</select>
</mapper>
mybatis使用步骤:
- 读取配置文件
- 创建SqlSessionFactory工厂
- 使用工厂生产SqlSession对象
- 使用SqlSession对象创建Dao接口的代理对象
- 使用代理对象执行方法
- 释放资源
InputStream in = Resources.getResourceAsStream(xmlPath); // 读取配置文件
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 构建者模式
SqlSessionFactory factory = builder.build(in); // 创建工厂,工厂模式
// autoCommit参数默认为false,我们可以将其设置为true,省略提交事务的步骤,但一般不采用,因为我们一般要将多个SQL语句组成一个事务,整体提交。
SqlSession session = factory.openSession(autoCommit=false); // 生产Session对象
IUserDao userDao = session.getMapper(IUserDao.class); // 创建Dao接口的代理对象
List<User> users = userDao.findAll(); // 使用代理接口执行方法
session.commit(); // 提交事务
session.close();
in.close(); // 释放资源
获取配置文件路径的安全方法:
- 使用类加载器,可以读取类路径的配置文件。
- 使用ServletContext对象的getRealPath()方法获取当前程序运行的路径。
mybatis支持用户不用代理接口,自己写实现类,但会增加许多冗余代码,一般不用。
注解配置
首先需要在主配置文件中的mapper标签下,使用class属性指定添加注解的全限定类名。在使用xml配置文件时需要使用resource属性指定映射配置文件的包地址
<mappers>
<mapper class="com.meituan.dao.IUserDao" />
</mappers>
SQL输入参数
输入参数:
在执行语句时,mybatis的输入可以是基本数据类型int,string或自定义数据类型。
当输入自定义数据类型时,在SQL语句中需要使用#{}或KaTeX parse error: Expected 'EOF', got '#' at position 36: …会自动利用OGNL进行解析。 #̲{}和{}的区别:#{}符号会在编译时将SQL语句中的对应字段变成?,通过prepareStatement中的set方法来赋值。能够有效避免SQL注入,更推荐使用。
${}符号只是在编译阶段将相应字段替换成变量值。
OGNL表达式:
(Object Graphic Navigation Language)对象图导航语言
这是一种通过对象的取值方式来获取数据的语言,在写法上忽略了get。
例如,通过类获取对象:user.getUsername();
通过OGNL表达式获取:user.username
- 当我们需要通过两个以上的类进行查询时,可以将这些类重新封装成另一个查询类(vo),再将这一查询类作为输入参数输入到SQL语句中。
- 解决实体类中的属性和表中的字段名称不同的问题:
在SQL语句上作修改,为查询后的字段取别名,此方法效率高,但需要需改每一条SQL语句。
添加配置,将查询后的字段和实体类中的属性一一对应,此方法效率低,但SQL语句无需修改,操作更方便。
<mapper namespace="com.meituan.domain.User">
<select id="findByVo" parameterType="com.meituan.domain.QueryVo" resultType="com.meituan.domain.User">
select * from user where username like #{user.username};
</selcet>
<!-- 方法1. 当实体类中的属性名和表中的词条名不同时,可以通过给查询后的词条起别名的方法,将查询结果封装到实体类中 -->
<select id="findAll" resultType="com.meituan.domain.User">
select id as userId, username as userName, sex as userSex, age as userAge, birthday as userBirthday from user;
</select>
<!-- 方法2. 将实体类中的属性和查询字段一一对应 -->
<!-- 此时,方法的返回值需要配置成resultMap="userMap",此方法效率不高,通常不用 -->
<resultMap id="userMap" type="com.meituan.domain.User">
<id property="userId" column="id" /id> <!-- 主键的配置,property为属性名,column为字段名 -->
<result property="userName" column="username" /result>
<result property="userSex" column="sex" /result> <!-- 非主键的配置,property为属性名,column为字段名 -->
<result property="userAge" column="age" /result>
<result property="userBirthday" column="birthday" /result>
<resultMap>
</mapper>
连接池
连接池一般用集合实现,连接池中存储着一些连接,他们在各个线程需要访问数据库时被各个线程调用,调用结束后放回连接池。类似于线程池。
连接池的特点是必须线程安全,且实现队列的特性。
可以在实际开发中节省许多创建连接的时间。
mybatis中的连接池:
mybatis提供了三种配置连接池的方式:
在主配置文件中,利用dataSource标签中的type属性配置连接池。
mybatis中的三种连接池配置:
POOLED:传统的javax.sql.DataSource规范中的连接池
UNPOOLED:不使用连接池,采用传统的获取连接方式
JNDI:采用服务器提供的JNDI技术来实现,获取DataSource 对象。如果不是web或者maven的war工程师不能使用的。
mybatis中连接池的调用原理:
mybatis中维持了两个连接池,分别是空闲池和活动池。
相应伪代码:
if 空闲池中有连接:
将空闲池中的连接拿出
else if 活动池未满:
在活动池中创建一个新的连接
else:
将最旧的(oldest)连接返回
Mybatis中的事务
事务的ACID特性:
- 原子性(Atomicity):事务的内部操作不可分割,一个事务要么全部成功,要么全部失败,并且失败时不能对数据库产生任何影响。
- 一致性(Consistency):事务的执行使数据库从一个状态转换成另一个状态,但不能影响数据库整体的稳定。
- 隔离性(Isolation):当多个用户同时访问数据库时,各个事务之间不能相互影响。
- 持久性(Durability):事务正确执行后,对数据库的影响是永久性的。
数据库的三范式:
第一范式:表中的字段具有原子性,即不可分割
第二范式:在第一范式的基础上,表中的所有非主键字段应该完全依赖于主键,即根据主键可以唯一的确定其他字段。
第三范式:在第二范式的基础上,表中的所有其他字段之间不存在依赖关系。
MyBatis中是使用sqlSession对象中的commit和rollback方法实现事务的提交和回滚。
动态SQL语句
MyBatis中可以动态的配置我们需要的SQL语句,具体可以通过以下标签实现:
if标签
我们可以通过if标签动态配置SQL语句,具体用法为
<select id="findByCondition" resultType="com.meituan.domain.User" parameterType="com.meituan.domain.User">
select * from user where 1=1
<if test="username != null"> <!-- if中test属性后面的是Java语句,username对应的是类中的属性名。 -->
and username = #{username} <!-- 可能接上的SQL语句,所以与要用and。前一个username是字段名称,后一个username是类中的属性名称 -->
</if>
<if test"sex != null">
and sex = #{sex}
</if>
</select>
select标签内为完整的SQL语句,当if条件成立时,将if标签内的语句与原语句拼接,否则忽视。
where标签
在使用if标签时,为了保证在if条件都不成立时SQL语句也能正常执行,我们通常要在SQL语句后面加上where 1=1,在MyBatis中我们可以利用where标签来去掉where 1=1操作
<select id="findByCondition" resultType="com.meituan.domain.User" parameterType="com.meituan.domain.User">
select * from user
<where>
<if test="username != null">
and username = #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>
foreach标签
在我们需要进行类似select * from user where id in(47, 48, 49);的查询时,foreach标签可以帮助我们动态配置SQL语句
<select id="findUsersById" parameterType="com.meituan.domain.IdList" resultType=>
select * from user
<where>
<if test="ids != null and ids.size() > 0"> <!-- 此处的与也用and -->
<foreach collection="ids" open="and id in (" close=")" item="id" seperate=",">
#{id}
</foreach>
</if>
</where>
</select>
在实际项目中,我们还可以通过sql标签提取重复的sql语句
<sql id="defalutSelect">
select * from user
</sql>
<select id="findByCondition" resultType="com.meituan.domain.User" parameterType="com.meituan.domain.User">
<include refid="defaultSelect" />
<where>
<if test="username != null"> <!-- if中test属性后面的是Java语句,username对应的是类中的属性名。 -->
and username = #{username} <!-- 可能接上的SQL语句,所以与要用and。前一个username是字段名称,后一个username是类中的属性名称 -->
</if>
<if test"sex != null">
and sex = #{sex}
</if>
</where>
</select>
注解配置
mybatis中的注解大致可以分为三类:
- 增删改查
@Insert @Update @Select @Delete - 结果集映射
- 缓存