一 概念
1.mybatis 是什么?
mybatis 是一个优秀的数据持久层框架。它支持自定义 SQL、存储过程和高级映射。
2.mybatis的前世今生
mybatis原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了 Google Code,随着开发团队转投Google Code 旗下, iBatis3.x正式更名为MyBatis。
3.mybatis的好处
mybatis是对jdbc进行轻量级的封装,提供专门xml文件来进行配置,以及可以自动的对查询结果进行封装,是一个ORM(java对象与数据库表映射)实现的数据持久层的框架,提供一些自己定义的类和接口来实现功能,支持动态sql,以及数据缓存.
Mybatis搭建
1.创建一个maven项目,添加mybatis,mysql依赖的jar
2.创建本地的git仓库
3.创建一个数据库表,以及一个应的java模型类
4.创建mybatis全局配置文件,配置数据库连接信息,配置sql映射文件
先创建xml文件
5.创建sql映射文件 定义一个与接口方法名相同的查询语句
6.测试mybatis
API 接口说明
SqlSessionFactory 接口
SqlSessionFactory封装了所有的配置信息,负责生成一个数据库连接的会话对象SqlSession对象,但创建开销比较大,所以在整个项目中只需要创建一次即可,不用销毁
SqlSession 接口
SqlSession 创建与数据库链接会话,类似于之前的Connection,接口中封装了对数据库操作的方法 ,SqlSessionFactory中的openSession()方法用来创建一个SqlSession对象,默认无参的默认设置事物提交为false(手动提交)
Mybatis 日志
具体选择哪个日志实现由 MyBatis 的内置日志工厂确定。它会使用最先找到的。 Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:
SLF4J|LOG4J|JDK_LOGGINGCOMMONS_LOGGING|STDOUT_LOGGING
配置日志 STDOUT_LOGGING"/>
参数传递
单个参数直接传递
首先我们在接口中创建一个抽象方法:
接下来我们在.xml文件中编写SQL语句:
然后我们直接在main方法中调用我们的抽象方法即可:
多个参数传递
使用@Param(“id”)绑定
在接口中创建一个抽象方法:
在.xml文件中编写SQL语句:
在main方法中调用我们的抽象方法即可:
增删改查
属性
1.namespace:
称为命名空间,用来将dao与Mapper进行绑定,namespace中的值要与所需dao的目录一致
2.id:
命名空间下的唯一标识符,其中的值为namespace中对应的接口中的方法名称
3.resultType:
SQL执行语句的返回值类型.
当返回值为对象或者对象的集合时,填写该对象的全限定名(包名+类名)
当返回值为字符串或者整数类型时,填写对应的类型.如: String,Intege
4.parameterType:
dao接口中方法的参数数据类型.其值为java数据类型全限定名(包名+类名)或Mybatis中定义的别名
#{} 和${}区别
#{} 占位符,是经过预编译的,编译好 SQL 语句再取值,#方式能够防止 sql 注入 #{}:select * from admin where uid=#{id} ${} 拼接符,会传入参数字符串,取值以后再去编译 SQL 语句,$方式无法防止 Sql 注入 ${} ${}:select * from admin where id= '1'
注意:MyBatis 排序时使用 order by 动态参数时需要注意,用$而不是#
代码
先编写Dao
在写配对的mapper
结果处理
我们在进行传统的jdbc代码进行增删改操作时,由于这些操作基本并不需要数据库的结果响应,所以相对来说比较简单.而对于查询操作,我们需要通过对ResultSet查询结果集进行处理解析后才能将其响应给客户端,而对于结果集的处理解析往往是一个非常繁杂的过程.
我们在使用传统的jdbc进行查询操作时,每次查询之后都需要将查询结果一一进行接收,当查询大量数据时这是一个非常庞大的工程.而在Mybatis中对于查询结果集的处理提供了处理功能.
1.简单类型输出映射
简单类型的查询返回简单的基本类型,我们以查询表中的记录条数为例
1.接口中的方法:
int findAdminCount();
2.mapper.xml中的配置文件:
<select id="findAdminCount" resultType="java.lang.Integer">
select count(*) from admin
</select>
3.测试类:
package com.ffyc.mybatisdemo.test;
import com.ffyc.mybatisdemo.dao.AdminDao;
import com.ffyc.mybatisdemo.model.Admin;
import com.ffyc.mybatisdemo.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class Test3 {
@Test
//简单类型输出映射
public void findAdminCount() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
int res = adminDao.findAdminCount();
System.out.println(res);
sqlSession.close();
}
}
2.对象输出映射
此处SQL语句查询结果为一条记录,Mybatis可以实现将记录自动封装到对象中.但是,这一切都是有前提条件的.
前提条件:
1.表中的列名与对象中的属性完全一致
2.开启全局配置中的mapUnderscoreToCameLCase,以实现查询结果中的列名与对象中的属性名自动转换.这一点我们在上面的Mybatis开发环境搭建的博客中配置过
<!--从经典的数据库命名转为java的驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
我们以使用id查询表中数据为例:
1.普通java实体类
package com.ffyc.mybatisdemo.model;
public class Admin {
private int id;
private String account;
private String password;
private String xb; //此处为xb,与数据库中列名不同,我们需特别注意
private String adminPhone;
public String getXb() {
return xb;
}
public void setXb(String xb) {
this.xb = xb;
}
public String getAdminPhone() {
return adminPhone;
}
public void setAdminPhone(String adminPhone) {
this.adminPhone = adminPhone;
}
public Admin() {
System.out.println("Admin无参构造");
}
public Admin(String account, String password, String gender) {
this.account = account;
this.password = password;
this.xb = gender;
}
public int getId() {
return id;
}
public void setId(int id) {
System.out.println("SetId");
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Admin{" +
"id=" + id +
", account='" + account + '\'' +
", password='" + password + '\'' +
", xb='" + xb + '\'' +
", adminPhone='" + adminPhone + '\'' +
'}';
}
}
2.接口中的方法:
Admin findAdminById(int id);
3.mapper.xml中的配置文件:
<select id="findAdminById" parameterType="int" resultType="Admin">
select * from admin where id = #{id}
</select>
4.测试类:
package com.ffyc.mybatisdemo.test;
import com.ffyc.mybatisdemo.dao.AdminDao;
import com.ffyc.mybatisdemo.model.Admin;
import com.ffyc.mybatisdemo.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
/*
结果处理
*/
public class Test3 {
@Test
//对象映射
public void findAdminById() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = adminDao.findAdminById(2);
System.out.println(admin);
sqlSession.close();
}
}
3.resultMap输出映射
resultMap可以将指定查询结果映射为普通的java对象,但是需要我们java实体类中的属性名与SQL查询结果中的列名一致.如果SQL查询结果与java实体类中的属性名不一致我们还可以通过特殊处理,使用resultMap将字段名与实体类中的属性名建立一一对应关系,从而实现将查询结果映射到普通java对象中.
首先我们需要了解resultMap中的各种属性:
resultMap属性列表:
id: resultMap的唯一标识符
type: resultMap所映射的实体对象类型
column: 字段名与属性名不一致时用来定义对应关系.column中的值为字段名
property: 字段名与属性名不一致时用来定义对应关系.property中的值为属性名
parameterType: 参数类型
3.1特殊处理定义resultMap
1.接口中的方法:
Admin findAdminById1(int id);
2.mapper.xml文件:
<!--特殊情况,单独处理-->
<resultMap id="adminMap" type="Admin">
<!--定义列名与属性名的对应关系-->
<id column="id" property="id"></id>
<result column="account" property="account"></result>
<result column="password" property="password"></result>
<result column="admin_phone" property="adminPhone"></result>
<result column="gender" property="xb"></result>
</resultMap>
<select id="findAdminById1" parameterType="int" resultMap="adminMap">
select * from admin where id = #{id}
</select>
3.测试类:
package com.ffyc.mybatisdemo.test;
import com.ffyc.mybatisdemo.dao.AdminDao;
import com.ffyc.mybatisdemo.model.Admin;
import com.ffyc.mybatisdemo.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
/*
结果处理
*/
public class Test3 {
@Test
//单独使用resultMap处理
public void findAdminById1() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = adminDao.findAdminById1(2);
System.out.println(admin);
sqlSession.close();
}
}
4.使用resultMap
1.接口中的方法:
Admin login1(Admin admin);
2.mapper,xml文件:
<select id="login1" resultMap="adminMap" parameterType="Admin">
select * from admin where account=#{account} and password=#{password}
</select>
注:
此处我们引用了resultMap为上述3.1中的“adminMap”
3.测试类:
package com.ffyc.mybatisdemo.test;
import com.ffyc.mybatisdemo.dao.AdminDao;
import com.ffyc.mybatisdemo.model.Admin;
import com.ffyc.mybatisdemo.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.io.IOException;
public class Test2 {
@Test
//使用 parameterType 参数进行类型定义
public void login1() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin1 = new Admin("admin", "111",null);
adminDao.login1(admin1);
sqlSession.close();
}
}
嵌套查询
将一个多表关联查询拆分为多次查询,先查询主表数据,然后查询关联表数据.
select:指定关联查询对象的 Mapper Statement ID 为 findDeptByID
column="dept_id":关联查询时将 dept_id 列的值传入 findDeptByID, 并将 findDeptByID 查询的结果映射到 Emp 的 dept 属性中
collection 和 association 都需要配置 select 和 column 属性,两者配置方法相同
注解方式
常用注解标签
@Insert @Select @Update @Delete @Param
动态sql
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号,还有臭名昭著苏的SQL拼接问题。利用动态 SQL,可以彻底摆脱这种痛苦。
动态SQL标签及属性列表:
where:
where标签解决了我们在之前SQL拼接问题中当传参为空时会出现“WHERE AND”情况,我们在传统的jdbc写法中为了解决这个问题,将“WHERE”改为了“WHERE 1=1”.使用where标签我们就再也不用遇到这种情况,where标签会自动检测如果它包含的标签中有返回值的话,它会自动插入where,再也不用我们去手动添加. 此外如果它包含的标签中返回内容为 AND 或者 OR 开头,则它会自动替换掉,十分方便.
<select id="findStudentList2" resultType="com.ffyc.mybatisdemo.model.Student" parameterType="Student">
select * from student
<where>
<if test="no!=null&no!=''">
no = #{no}
</if>
<if test="name!=null&name!=''">
name = #{name}
</if>
</where>
</select>
if:
if标签通常用于WHERE语句,UPDATE语句,INSERT语句中,通过判断参数值来决定是否使用某个查询条件,判断是否更新某一个字段,判断是否插入某一个字段的值
foreach:
foreach标签主要用于构建in条件,可在SQL中对集合进行迭代.也常用到批量删除,添加操作中.
<select id="findStudentlist1" resultType="com.ffyc.mybatisdemo.model.Student">
select * from student where no in
<foreach collection="list" item="no" open="(" separator="," close=")">
#{no}
</foreach>
</select>
trim:
格式化输出,也可用来设定或忽略前后缀
<select id="findStudentList2" resultType="com.ffyc.mybatisdemo.model.Student" parameterType="Student">
select * from student
<trim prefix="where" prefixOverrides="and|or">
<if test="no!=null&no!=''">
no = #{no}
</if>
<if test="name!=null&name!=''">
name = #{name}
</if>
</trim>
</select>
collection:
collection属性的值有三个分别为: list,array,map 对应的参数类型分别为: LIst,数组,map集合
item:
表示在迭代过程中每一个元素的别名
open:
前缀
close:
后缀
separator:
分隔符,表示迭代时每个元素之间以什么分隔
<select id="findStudentlist1" resultType="com.ffyc.mybatisdemo.model.Student">
select * from student where no in
<foreach collection="list" item="no" open="(" separator="," close=")">
#{no}
</foreach>
</select>
choose:
从多个选项中选择一个.按照顺序判断when中的条件是否成立,有一个成立则choose结束.当choose中所有的when都不成立时,则执行otherwise中的SQL.类似于java中的switch case default.
<select id="findStudentList2" resultType="com.ffyc.mybatisdemo.model.Student" parameterType="Student">
select * from student
<trim prefix="where" prefixOverrides="and|or">
<choose>
<when test="no!=null&no!=''">
no = #{no}
</when>
<otherwise>
name = #{name}
</otherwise>
</choose>
</trim>
</select>
set:
没有if标签时,如果有一个参数为null,都会导致错误.当在update语句中使用if标签时,如果最后面的if没有执行,则会导致逗号多余的错误.使用SET标签可以动态的配置set关键字,和剔除追加到条件末尾的任何不相关的逗号.
<update id="updateStudent" parameterType="Student">
update student
<set>
<if test="name!=null">
name = #{name},
</if>
<if test="gender!=null">
gender = #{gender},
</if>
<if test="no!=null">
no = #{no}
</if>
</set>
where id = #{id}
</update>
我们在上述标签介绍时在xml文件中示例了每个标签及属性的使用方法,因此在此处我们只示例一个简单查询列表的测试方法:
1.接口中的方法:
//动态SQl
List<Student> findStudentList2(Student student);
2.mapper.xml文件:
<select id="findStudentList2" resultType="com.ffyc.mybatisdemo.model.Student" parameterType="Student">
select * from student
<where>
<if test="no!=null&no!=''">
no = #{no}
</if>
<if test="name!=null&name!=''">
name = #{name}
</if>
</where>
</select>
3.测试类:
@Test
//动态SQL
public void findStudentlist2() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentDao2 studentDao2 = sqlSession.getMapper(StudentDao2.class);
Student student = new Student();
student.setNo(103); //此处我们动态传入no值进行查询
List<Student> students = studentDao2.findStudentList2(student);
System.out.println(students);
sqlSession.close();
}
特殊符号处理
在mybatis的xml文件中经常存在一些特殊字符,比如:< > " & <>
这些符号正常书写时,无法转义会报错,所以要对这些符号进行转义处理。
特殊字符 转义字符
< <
> >
" "
' '
& &
还有一种方法,用<![CDATA[ ]]>包裹特殊字符
<if test = "id!=null">
AND <![CDATA[ id<#{id} ]]>
</if>
<![CDATA[ ]]>是xml语法但要注意以下几点:
1.使用动态SQL时要像if、foreach、where等标签一但被 <![CDATA[ ]]>标签包裹,将忽略xml的解析并出错
2.<![CDATA[ ]]>标签中不可嵌套<![CDATA[ ]]>标签
3.<![CDATA[ ]]>尽量缩小范围,以免出错
缓存(Cache)
作用
是为了减轻数据库的压力,提高查询性能。
原理
程序运行时,从数据库中查询到对象在使用完后不销毁,而是存储在内存(缓存)里,再次使用该数据时,直接从内存中获取,不必再从数据库中查询,减少了查询次数。而数据库中的数据存放在电脑硬盘中,如此加快了查询速度。
Mybatis 有一级缓存和二级缓存。
一级缓存
是sqlSession级别的,在同一个sqlSession中执行两次相同的sql语句,第一次执行完毕会将查询到的数据写入缓存中,第二次执行直接从缓存中获取数据。当sqlSession结束后,该sqlSession中的一级缓存也就不存在了(sqlSession.close();),还可以通过sqlSession.clearCache();直接清空缓存,其次SqlSession 中执行了任何一个 update 操作(update()、delete()、 insert()) ,都会清空缓存的数据。一般Mybatis默认一级缓存。
二级缓存
是sqlSessionFactory级别的,将查询到的数据存放到二级缓存就可实现多个sqlSession共享,第一次查询到数据后,关闭sqlSession时数据存入二级缓存中。
二级缓存需要配置
第一步:启用二级缓存 在 SqlMapperConfig.xml 中启用二级缓存,如下代码所示,当 cacheEnabled 设置为 true 时启用二级缓存,设置为 false 时禁用二级缓存。
<setting name="cacheEnabled" value="true"/>
第二步:对象序列化 将所有的 POJO 类实现序列化接口 Java.io. Serializable。
第三步:配置映射文件 在 Mapper 映射文件中添加<cache/>,表示此 mapper 开启二级缓存。 当 SqlSeesion 关闭时,会将数据存入到二级缓存,还可根据自己所需添加其他限制如:<cache/ size = "这个是容量" flushIntervd = "这个是缓存保存时间" />
反射
概念
Java反射机制是运行程序时,可以对任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它任意的方法和属性;这种动态获取信息及动态调用对象方法的功能称为Java语言的反射机制。简而言之,可以动态获取任意类信息,以及创建类的对象,以及调用对象的属性和方法。
作用:
动态获取类的信息。
好处:写一个方法可以处理任何类。
相关API
Class类型 Constructor 构造方法 Method 方法 Field 属性,除了Class外,其他都位于java.lang.reflect包中。API将类的类型,方法,属性都封装成了类。
Class类
Class类是Java反射机制的基础,通过Class类,可以得到一个类的基本信息。一旦class文件被加载到内存,就会为其创建一个Class对象。任何类被使用时都会创建一个Class对象。
获取Class类的方法:
1.Object类中的getClass方法:适用于通过对象获得Class实例的情况
2.类名.class方式:适用于通过类名获得Class实例的情况
3.Class类的静态方法 forName(String name),通过类的地址加载类,获得到类的Class对象
获得Constructor类实例
Constructor对象通过Class对象获得,Class类中定义了下面的方法:
Constructor getConstructor(Class... parameterTypes)
通过指定参数类型,返回构造方法实例。
获得构造方法
Constructor类可以通过getXXX方法获得构造方法的基本信息,例如:
可以拿到类中的公共的无参构造方法;
可以拿到类中的公共的有参构造方法;
可以拿到类中的公共的或私有的无参构造方法
可以拿到类中的公共的或私有的有参构造方法
创建实例
除了获得构造方法的基本信息,还可以创建实例
例如:
这是通过公共权限的构造方法创建的对象,但是私有的构造方法有点需要注意,需要设置对私有权限构造方法进行操作后,才可创建对象,如下:
获得Field实例
Field类
将类的属性进行封装可以获得属性的基本信息、属性的值,也可以对属性进行赋值.
获得类中属性:
getField(name) 获得的是指定名称公共权限属性
getFields() 获得所有公共权限属性
getDeclaredField("name");获得的是指定名称私有权限属性
getDeclaredFields();获得所有属性
getName:返回属性名
set:设置属性值
获得Method实例
Method实例都也是通过Class类的方法获得,
c.getMethods()获得类中所有的公共的成员方法
getMethod("getId",type) 获得公共的指定名称参数的成员方法
c.getDeclaredFields();得类中所有的成员方法(包含私有的)
c.getDeclaredMethod(name,type)
了动态获得方法信息外,Method还能动态调用某一个对象的具体方法invoke(为以后动态代理做铺垫)即调用方法。设值还是取值要看具体的方法
method.invoke(Object obj,Object... args)
反射案例:自定义java对象转json工具类
反射优缺点
优点
可以在运行时动态获取任意一个类的信息,创建对象,调用对象的方法、属性,提高代码的灵活性,复用性。
缺点
需要对类信息进行解析判断,效率低;使用反射机制可以操作私有信息,会打破封装性。