MyBatis
框架的介绍
软件框架:框架就是制定一套规范或者规则(思想),大家(程序员)在该规范或者规则(思想)下工作。或者说使用别人搭好的舞台来做编剧和表演,框架基于jar包和xml文件。
MyBatis框架
-
是dao层的框架,对dao层的封装。
-
是一个半自动化的ORM框架
-
特点:
- 完成java对象和关系型数据库数据的转换,可以封装数据源。
- 将sql语句和java代码分离,降低耦合度。
- 支持缓存机制。
- 将表与表之间的关系转换为实体与实体之间的关系。
-
工作流程
加载驱动------>Connection-------->Preparstatement--------->ResultSet
解析配置文件------>SqlSessionFactory-------SqlSession
-----Excutor(执行器)------MapperedStatement
框架入门程序搭建
1.创建项目,加入依赖包
2.编写配置文件
- 全局配置文件:数据源和映射文件
<?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">
<configuration>
<!-- 数据源 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatistest" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="com/fly/bean/UserMapper.xml" />
</mappers>
</configuration>
- 映射文件:sql语句
准备数据库以及表
创建对应的实体类User
UserMapper.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:命名空间,可以任意定义 -->
<mapper namespace="org.mybatis.example.BlogMapper">
<!-- id:在当前的命名空间下唯一, 可以任意定义,parameterType:输入参数类型,resultType:输出参数类型(包+类名)-->
<select id="selectUser" parameterType="int" resultType="com.fly.bean.User">
<!--#{}等价于占位符? 输入参数是数字类型,#{任意定义} -->
select * from user where id = #{pid}
</select>
</mapper>
- 解析配置文件并调用sql语句
public class App {
public static void main(String[] args) throws IOException {
//全局配置文件的路径
String resource = "MybatisConfig.xml";
//将全局配置文件转化到输入流中
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过全局配置文件的输入流得到SqlSessionFactory(会话工厂)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过会话工厂获得会话对象
SqlSession ss = sqlSessionFactory.openSession();
//通过会话对象调用sql语句,参数一:映射文件中对应的sql语句的id,参数二:占位符#{}的值
User u=ss.selectOne("org.mybatis.example.BlogMapper.selectUser", 1);
System.out.println(u);
}
}
控制台sql日志打印
- 加入日志依赖包
- 编写日志的文件log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
MyBatis中的Mapper代理模式
简单介绍
entity----dao----service----controller
dao,service层面向接口开发(UserDao,UserDaoImpl)
以mybatis完成传统的dao层开发
代理:A遵循一定协议或者规范的前提下,帮助B完成一部分额外的业务。
Mapper代理:程序员在开发的过程中遵循Mybatis的某些规范后,Mybatis会帮我们生成Dao层的实现类。
mapper代理的规范:映射文件和Dao层接口之间
- 映射文件的命名空间是接口的全路径;
- 映射文件的sql语句的id=接口里的方法名;
- 映射文件的sql语句的输入输出参数和接口里的方法的输入参数以及返回结果保持一致。
实体类bean(User实体)
package com.fly.bean;
import java.util.Date;
public class User {
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 + "]";
}
}
###映射文件UserMapper.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.fly.dao.UserDao">
<select id="getUserById" parameterType="int" resultType="com.fly.bean.User">
select * from user where id=#{uid}
</select>
</mapper>
持久层dao
dao层接口UserDao
public interface UserDao {
public User getUserById(int id);
}
业务service(这里通过主函数调用测试dao层)
public class UserServiceImpl {
public static void main(String[] args) throws IOException {
//全局配置文件的路径
String resource = "MybatisConfig.xml";
//将全局配置文件转化到输入流中
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过全局配置文件的输入流得到SqlSessionFactory(会话工厂)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过会话工厂获得会话对象
SqlSession ss = sqlSessionFactory.openSession();
//获取接口对应的实现类(Mybatis框架在内存中生成)的类对象
UserDao udao = ss.getMapper(UserDao.class);
User user = udao.getUserById(5);
System.out.println(user);
}
}
配置文件说明
- properties标签
<!-- 引入外部的properties文件 -->
<properties resource="db.properties">
<!-- <property name="db_driver" value="com.mysql.jdbc.Driver"/>
<property name="db_url" value="jdbc:mysql://localhost:3306/mybatistest"/>
<property name="db_uname" value="root"/>
<property name="db_pwd" value="root"/> -->
</properties>
- Setting标签:用来设置mybatis的一些运行行为,例如缓存,延迟加载,是否开启自动驼峰命名规则(camel case)映射等。
<!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- typeAliases标签:类型别名,是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
<typeAliases>
<!-- 单个起别名,type:自定义类型,alias:别名 -->
<!-- <typeAlias type="com.fly.bean.User" alias="u"/> -->
<!-- 为包里的类批量起别名,默认别名是类名或者把类名的首字母小写 -->
<package name="com.fly.bean"/>
</typeAliases>
- environments:数据源环境,可以包含多个数据源环境。
<environments default="test">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db_driver}" />
<property name="url" value="${db_url}" />
<property name="username" value="${db_uname}" />
<property name="password" value="${db_pwd}" />
</dataSource>
</environment>
</environments>
- databaseIdProvider:数据库厂商标志,可以根据不同的厂商标志执行不同的sql语句
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
- mappers:用来管理映射文件,共有四种方式可以使用
<mappers>
<!-- 方式一:指定映射文件相对src的路径 -->
<!-- <mapper resource="com/fly/bean/UserMapper.xml"/> -->
<!-- 方式二:指定映射文件在硬盘的路径 -->
<!-- <mapper url="file:你的xml文件路径"/> -->
<!-- 接口和映射文件必须同包同名 -->
<!-- 方式三:指定和映射文件同包同名的接口的路径 -->
<!-- <mapper class="com.fly.dao.UserDao"/> -->
<!-- 方式四:批量管理映射文件 -->
<package name="com.fly.dao"/>
</mappers>
动态sql
- resultMap:完成结果的手动映射,一般结果集的列名和实体的属性名不一致时使用。
<!--type:返回结果的类型,id:任意定义在当前命名空间唯一 -->
<resultMap type="user" id="rs">
<!--完成主键映射,column:列名 ,property:属性名 -->
<id column="uid" property="id"/>
<!--完成非主键映射 -->
<result column="uname" property="username"/>
</resultMap>
<select id="#" parameterType="int" resultMap="rs">
select id uid,username uname from user where id=#{uid}
</select>
- sql片段:提取sql语句的公共部分,统一维护。
定义:
<sql id="任意定义,要在当前命名空间唯一的">
select * from user
</sql>
引用:
<include refid="sql片段的id"></include>
- foreach:遍历标签,遍历List集合和数组。
<!-- foreach -->
<delete id="#" parameterType="java.util.List">
delete from user where id in
<!--collection:输入参数为List集合时,必须写list,
输入参数为array数组时,必须写array,
输入参数为自定义类型时,必须写实体类的属性名,
item:为集合里的每一项起名,可以任意定义
separator:每一项中间的分割符
open:在执行循环体之前拼接的内容;
close:在执行循环体之后拼接的内容;-->
<foreach collection="list" item="uid" separator="," open="(" close=")">
#{uid}
</foreach>
</delete>
- where if:一般用作条件检索
<!--where:解析成where关键字,会自动去掉第一个符合条件的限定条件中的and -->
<select id="#" parameterType="map" resultType="user">
select * from user
<where>
<if test="uname!=null and uname!=''">
and username like "%"#{uname}"%"
</if>
<if test="usex!=null and usex!=''">
and sex=#{usex}
</if>
</where>
</select>
- choose when orherwise:一般用作条件检索
<!--choose when otherwise:某个判断满足条件后,其他条件就不会再执行 -->
<select id="#" parameterType="map" resultType="user">
select * from user where
<choose>
<when test="uname!=null and uname!=''">
username like "%"#{uname}"%"
</when>
<when test="usex!=null and usex!=''">
sex=#{usex}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</select>
- set if:用于动态更新某些字段
<!-- set:解析为set关键字,可以自动去掉最后一个更新的字段后面的逗号 -->
<update id="#" parameterType="user">
update user
<set>
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="sex!=null and sex!=''">
sex=#{sex}
</if>
</set>
where id=#{id}
</update>
- trim:格式化sql语句,同时设置sql的前后缀。
<update id="updUser1" parameterType="user">
<!--prefix:前缀,在trim中内容执行之前拼接
suffix:后缀:在trim中内容执行之后拼接
suffixOverrides:忽略后缀
prefixOverrides:忽略前缀-->
<trim prefix="update user set" suffix="where id=#{id}" suffixOverrides=",">
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="sex!=null and sex!=''">
sex=#{sex}
</if>
</trim>
</update>
- bind:在输入字符串的基础拼接一些其他的字符组成一个新的字符串。
<select id="#" parameterType="string" resultType="User">
<!--name:新的字符串的变量名 -->
<bind name="uname" value="'%'+_parameter+'%'"/>
select * from user where username like #{uname}
</select>
关系映射
处理关系映射时可以采用自动映射和手动映射(关联查询,嵌套查询(支持延迟加载))
一对一
- 自动映射: 先编写级联查询的sql语句,创建一个类包含结果集里的所有列名作为属性名。
映射文件:
<!-- 1-1:自动映射 -->
<select id="oneToOne" resultType="UserView">
select u.*,c.num from user u,card c where u.id=c.per_fk
</select>
- 手动映射
1.把表和表之间的关系转换为实体和实体之间的关系,在其中一方加入另一方的实体类型属性(在User类中加Card类型的属性)
2. 在映射文件中完成手动映射,关联查询:通过一条sql语句查到结果,resultMap完成映射。
<!-- 1-1:手动映射之级联查询 -->
<resultMap type="user" id="oTors">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<!--association:完成自定义类型的映射
property:User类中自定义类型(Card)的属性名
javaType:指定对应属性的类型-->
<association property="card" javaType="card" resultMap="cardrs">
<result property="num" column="num"/>
</association>
</resultMap>
<select id="oneToOne1" resultMap="oTors">
select u.*,c.num from user u,card c where u.id=c.per_fk
</select>
- 嵌套查询:先查询主信息表的信息,然后再通过其他sql语句查询关联的信息。
<!-- 1-1:手动映射之嵌套查询(分步) -->
<resultMap type="User" id="oTors2" >
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<!-- select:调用另外一条sql语句(命名空间.sql语句id)
column:在第一个结果集中用来关联查询的列名 -->
<association property="card" javaType="card"
select="com.offcn.fly.UserDao.getCardByUid" column="id"></association>
</resultMap>
<select id="oneToOne2" resultMap="oTors2">
select * from user
</select>
<select id="getCardByUid" parameterType="int" resultType="card">
select * from card where per_fk=#{uid}
</select>
一对多
用户(1)和订单(n)
手动映射
- 在用户的实体里添加订单的集合属性,set,get方法。
- 映射文件
<!-- 1-n:关联查询 -->
<resultMap type="Orders" id="orderrs">
<id column="oid" property="id"></id>
<result column="number" property="number"/>
</resultMap>
<resultMap type="user" id="oTmrs" extends="commrs">
<collection property="olist" ofType="Orders" resultMap="orderrs">
<id column="oid" property="id"></id>
<result column="number" property="number"/>
</collection>
</resultMap>
<select id="oneToMany" resultMap="oTmrs">
select u.*,o.id oid,o.number from user u,orders o where u.id=o.userId
</select>
嵌套查询的映射文件
<!--1-n:嵌套查询-->
<resultMap type="user" id="oTmrs2">
<collection property="olist" ofType="Orders"
select="com.offcn.dao.UserDao.getOrdersByUid" column="id"></collection>
</resultMap>
<select id="oneToMany2" resultMap="oTmrs2">
select * from user
</select>
<select id="getOrdersByUid" parameterType="int" resultType="Orders">
select * from orders where userId=#{uid}
</select>
多对多
用户和商品:查询某个用户购买所有的商品(商品购买的订单号,下单人的信息,以及商品购买的数目)
<!-- 查询某个用户购买所有的商品(商品购买的订单号,下单人的信息,以及商品购买的数目) -->
<resultMap type="user" id="mTmrs">
<result column="username" property="username"/>
<collection property="olist" ofType="Orders">
<result property="number" column="number"/>
<collection property="dlist" ofType="Orderdetail">
<result column="itemsNum" property="itemsNum"/>
<association property="items" javaType="Items">
<result column="name" property="name"/>
<result column="price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
<select id="manyToMany" resultMap="mTmrs">
select u.username,o.number,i.name,i.price,d.itemsNum
from user u,orders o,orderdetail d,items i
where u.id=o.userId and o.id=d.ordersId and d.itemsId=i.id
</select>
延迟加载
先加载主信息表的查询,在需要关联的信息时,再查询对应的关联信息。
配置:
在全局配置文件启用延时加载
<setting name="lazyLoadingEnabled" value="true"/>
注意:延时加载仅针对与嵌套查询。
缓存机制
- 为避免频繁的从数据库读取数据,所以会常用作查询的数据放到缓存中。
- Mybatis内部提供的缓存策略有两种
一级缓存:mybatis框架内置提供,不需要开发人员做任何配置。SqlSession级别的缓存,每一个SqlSession只能使用自己的一级缓存区域数据,不可以跨sqlSession共享一级缓存。在执行增删改之后一级缓存域的数据会被自动清除掉,也可以手动清除,调用clearCache();
二级缓存:Mapper接口级别的缓存。不同的sqlSession操作同一个Mapper接口可以共享二级缓存区域的数据。
手动设置: - 指定二级缓存的类型(mybatis框架提供的二级缓存实现类,也可以使用第三方插件实现mybatis的二级缓存(ehcahce,redis)),在增删改后二级缓存区域的数据也会被清除掉。
<!-- 指定二级缓存的类型,type省略不写默认是自带的-->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"></cache>
- 序列化缓存的对象。