Mybatis概述
MyBatis是一款优秀的持久层ORM(Object Relation Mapping)框架
MyBati内部封装了jdbc,使开发者只需要关注sql语句本身而不需要话费精力加载驱动,创建连接。创建statement等复杂工程,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
所谓ORM就是一种为了解决面向对象与关系型数据库类型不匹配的技术。通过java对象与数据库表之间的映射关系自动将java应用程序中的对象持久化到关系型数据库表中。
使用ORM框架后,应用程序不再直接访问底层数据库,而是以面向对象的方式持久化对象,而ORM框架则会通过映射关系将这些面向对象的操作转换成底层的sql操作。
使用步骤
1在根目录下创建mybatis的核心配置文件mybatis-config.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">
<configuration>
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
2根据数据表创建实体类,采用驼峰命名法属性对应字段名
3写dao接口和映射文件mapper.xml
package com.lanou.dao;
import com.lanou.pojo.Employee;
public interface EmployeeMapper {
Employee selectEmployeeById(int id);
}
mapper.xml中的namespace是接口的类全限定名,id是接口的方法名
<?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.lanou.dao.EmployeeMapper">
<select id="selectEmployeeById" resultType="com.lanou.pojo.Employee">
select <include refid="selectColumn"></include>from t_employee where id=#{id}
</select>
</mapper>
4测试类
@Test
public void getEmployeeById(int id){
SqlSession sqlSession =null;
try {
sqlSession=sqlSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectEmployeeById(1);
System.out.println(employee);
} catch (IOException e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
private SqlSession sqlSession() throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sessionFactory.openSession();
return sqlSession;
}
Mybatis操作如下:
1读取配置文件,2根据配置文件构建SqlSessionFactory,3通过SqlSessionFactory创建sqlSession,4使用SqlSession对象操作数据库(查询、添加、修改、删除,提交事务)
Mybatis的两个核心对象
SqlSessionFactory和SqlSession
SqlSessionFactory对象的实例通过SqlSessionFactoryBuilder对象来构建,通过xml配置文件或预先定义好的configuration实例构建出SqlSessionFactory的实例
SqlSessionFactory对象是线程安全的,一旦被创建,整个应用执行期间都会存在
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession是一个单线程对象,底层封装了JDBC连接,可以直接使用其来执行已映射的sql语句
SqlSession实例不能被共享,线程不安全,使用过后在finally块中关闭
增删改查
insert
常用属性 | 说明 |
---|---|
keyProperty | 将插入或更新操作的返回值赋给pojo类的某个属性,通常设置为主键对应的属性 |
useGenerateKeys | 获取数据库自增生成的字段,默认值为false |
如果使用的数据库支持主键自动增长,那么可以通过keyProperty属性指定pojo类的某个属性接收主键返回值(通常会设置到id属性上)然后将useGeneratedKeys的属性值设置为true,配置之后执行插入操作会返回插入成功的行数和插入行的主键值
插入一条数据并返回主键
<insert id="insertEmployeeReturnId" parameterType="com.lanou.pojo.Employee" useGeneratedKeys="true"
keyProperty="id">
insert into t_employee(last_name,email,gender)values (#{lastName},#{email},#{gender})
</insert>
插入多条数据
<insert id="insertEmployees" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
insert into t_employee(last_name,email,gender)values
<foreach collection="list" item="employee" index="index" separator=",">
(
#{employee.lastName},
#{employee.email},
#{employee.gender}
)
</foreach>
</insert>
select
常用属性 | 说明 |
---|---|
id | 命名空间的唯一标识符 |
parameterType | SQL语句参数类的全限定名或别名,,是可选属性,mybatis可以通过TypeHandler推断出具体传入语句的参数 |
resultType | SQL语句返回类型的类的全限定名或别名,如果是集合类型,那么返回的应该是集合可以包含的类型,而不是集合本身,返回时可使用resultType或resultMap之一 |
resultMap | 表示外部resultMap的命名引用,返回时可使用resultType或resultMap之一 |
根据主键查询数据,parameterType才用mybatis的TypeHandler自动判断
<sql>元素:定义可重用的sql代码片段,然后在其他语句中 <include refid="selectColumn"/>引用
<sql id="selectColumn">
id,last_name lastName, email,gender
</sql>
<!--根据主键查数据-->
<select id="selectEmployeeById" resultType="com.lanou.pojo.Employee">
select <include refid="selectColumn"></include>from t_employee where id=#{id}
</select>
多个参数查询数据,采用@Param("")起别名
<select id="selectEmployeeByNameAndGender" resultType="com.lanou.pojo.Employee">
select id,last_name lastName, email,gender from t_employee where last_name=#{name} and gender=#{gender}
</select>
查询多个数据,返回时可使用resultType或resultMap之一
resultMap元素的属性、子元素 | 功能 |
---|---|
type属性 | 需要映射的pojo |
id属性 | 是resultMap的唯一标识, |
子元素constructor | 用于配置构造方法(当一个pojo未定义无参的构造方法时,使用该元素进行配置) |
子元素id | 用于表示哪个列名是主键 |
子元素result | 用于表示pojo和数据表中的普通列的映射关系 |
子元素 association和collection | 用于处理多表时的关联关系 |
<resultMap id="empMap" type="com.lanou.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
<select id="selectAllEmployee" resultMap="empMap">
select id,last_name lastName,email,gender from t_employee
</select>
<select id="selectEmployeeReturnMap" resultType="com.lanou.pojo.Employee">
select id,last_name lastName,email,gender from t_employee
</select>
association
关联关系:一对一
association的属性 | 作用 |
---|---|
property | 指定映射到的实体类对象属性,与表字段一对一对应 |
column | 指定表中对应的字段 |
javaType | 指定映射到实体对象属性的类型 |
select | 指定引入嵌套查询的字SQL语句,用于关联映射中的嵌套查询 |
fetchType | 指定在关联查询时是否启用延迟加载,lazy和eager两个属性,默认是lazy |
<resultMap id="empMap1" type="com.lanou.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept" javaType="com.lanou.pojo.Dept">
<id column="did" property="id"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="empMap1">
SELECT e.id,e.last_name,e.email,e.gender,d.id did,d.dept_name FROM t_dept d LEFT JOIN t_employee e on
d.id=e.d_id
</select>
collection
关联关系:一对多
collection大部分属性与association相同,但包含一个特殊属性ofType,该属性与javaType属性对应,用于指定实体对象中集合类属性所包含的元素类型
<resultMap id="dept1" type="com.lanou.pojo.Dept">
<id column="eid" property="id"/>
<result column="dept_name" property="deptName"/>
<collection property="employees" javaType="java.util.ArrayList" ofType="com.lanou.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<select id="getAllEmp" resultMap="dept1">
SELECT d.id,dept_name,e.id eid,e.last_name,e.email,e.gender FROM t_dept d JOIN t_employee e on
d.id=e.d_id
</select>
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载, association 指的就是一对一, collection 指的就是一对多查询。在 Mybatis 配置文件中, 可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
它的原理是, 使用 CGLIB 创建目标对象的代理对象, 当调用目标方法时, 进入拦截器方法, 比如调用 a.getB().getName(), 拦截器 invoke()方法发现 a.getB()是null 值, 那么就会单独发送事先保存好的查询关联 B 对象的 sql, 把 B 查询上来, 然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。
update
使用set和if元素组装update语句,让程序只更新需要更新的字段
<update id="updateEmpById" parameterType="com.lanou.pojo.Employee">
update t_employee
<set>
<if test="employee.lastName!=null and employee.lastName!=''">
last_name=#{employee.lastName}
</if>
<if test="employee.email!=null and employee.email!=''">
email =#{employee.email}
</if>
<if test="employee.gender !=null and employee.gender!=''">
gender=#{employee.gender}
</if>
</set>
where id=#{id}
</update>
delete
<delete id="deleteEmployeeById" parameterType="int">
delete from t_employee where id=#{id}
</delete>
mybatis动态SQL
Mybatis 动态 sql 可以在 Xml 映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接 sql 的功能。
Mybatis 提供了 9 种动态 sql 标签:trim | where | set | foreach | if | choose| when | otherwise | bind 。
标签 | 作用 |
---|---|
if | 用于单条件分支判断 |
choose (when otherwise) | 用于多条件分支判断 |
where、trim、set | 辅助元素,用于处理一些SQL拼装,特殊字符问题 |
foreach | 循环语句,用于in语句等列举条件 |
bind | 从OGNL表达式中创建一个变量,将其绑定到上下文,用于模糊查询的sql中 |
即使where之后的内容有多余的and where元素会自动将他们去除。
<select id="getByCondition" resultMap="empMap">
<bind name="_lastName" value="'%'+lastName+'%'"/>
select <include refid="selectColumn"></include>from t_employee
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{_lastName}
</if>
<if test="email!=null and email !=''">
and email=#{email}
</if>
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</where>
</select>
使用trim做到和where相同的功能:
prefix属性代表的是语句的前缀,prefixOverrides属性代表需要去除的那些特殊字符串
<select id="getByCondition" resultMap="empMap">
<bind name="_lastName" value="'%'+lastName+'%'"/>
select <include refid="selectColumn"></include>from t_employee
<trim prefix="where" prefixOverrides="and">
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null and lastName!=''">
and last_name like #{_lastName}
</if>
<if test="email!=null and email !=''">
and email=#{email}
</if>
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</trim>
</select>
foreach
item:配置的是循环中当前的元素
collection:配置的是传来的参数类型(首字母小写)可以是array、list等
open,close:配置的是以什么符号将这些集合元素包装起来
separator:配置的是各个元素的间隔符
<delete id="deleteEmployeeByIds" parameterType="java.util.Arrays">
delete from t_employee where id in
<foreach collection="array" item="idArray" separator="," open="(" close=")">
#{idArray}
</foreach>
</delete>
collection的3种情况:
- 如果传入的是单参数且参数类型是一个数组或者List时,collection属性分别是:array、list
- 如果传入的参数有多个,就需要把它们封装成一个Map,这时collection的属性值就是Map的键
- 如果传入的参数pojo包装类,collection属性值就是该包装类中需要进行遍历的数组或集合的属性名
分页
导入jar包
在配置文件中配置
<configuration>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
</configuration>
使用
pageHelper拦截器在查询之前将数据分页
private void getAllEmployee(){
SqlSession sqlSession =null;
try {
sqlSession=sqlSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Page<Object> page = PageHelper.startPage(1, 5);
List<Employee> employees = employeeMapper.selectAllEmployee();
for (Employee employee : employees) {
System.out.println(employee);
}
System.out.println("当前页码"+page.getPageNum());
System.out.println("总记录数"+page.getTotal());
System.out.println("每页的记录数"+page.getPageSize());
System.out.println("总页码"+page.getPages());
} catch (IOException e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
mybatis缓存
mybatis有一级缓存(本地缓存)和二级缓存(全局缓存),一级缓存是一直开启的,二级缓存需要配置
- 开启全局二级缓存配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 去mapper.xml中配置使用二级缓存:
<cache></cache>
- POJO需要实现序列化接口
第三方缓存整合:
- 1)、导入第三方缓存包即可;
- 2)、导入与第三方缓存整合的适配包;官方有;
- 3)、mapper.xml中使用自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
mybatis四大组件
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
拦截器和过滤器的区别
过滤器 Filter,是 JavaEE 的标准,依赖于 Servlet 容器,使用的时候是配置在 web.xml 文件中的,可以配置多个,执行的顺序是根据配置顺序从上到下。常用来配置请求编码以及过滤一些非法参数,垃圾信息或者是网站登录验证码。
1)过滤器(Filter):它依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<servlet-name>/*</servlet-name>
</filter-mapping>
2、拦截器(Interceptor)
拦截器的配置一般在SpringMVC的配置文件中,使用Interceptors标签,具体配置如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.scorpios.atcrowdfunding.web.LoginInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.scorpios.atcrowdfunding.web.AuthInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
(2)拦截器(Interceptor):它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
过滤器的运行是依赖于servlet容器,跟springmvc等框架并没有关系。并且,多个过滤器的执行顺序跟xml文件中定义的先后关系有关。
两者的本质区别:拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的