学会MyBatis的CRUD基本操作其实也不是什么难事
MyBatis动态sql以及主配置文件中的内容
一、MyBatis对象分析
(1)Resources类
Resources类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的IO流对象。
(2)SqlSessionFactoryBuilder类
SqlSessionFactory的 创 建 , 需 要 使 用SqlSessionFactoryBuilder对 象 的build()方 法 。 由 于SqlSessionFactoryBuilder对象在创建完工厂对象后,就完成了其历史使命,即可被销毁。所以,一般会将该SqlSessionFactoryBuilder对象创建为一个方法内的局部对象,方法结束,对象销毁。
(3)SqlSessionFactory接口
SqlSessionFactory接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。
创建SqlSession需要使用SqlSessionFactory接口的的openSession()方法。
-
openSession(true):创建一个有自动提交功能的SqlSession
-
openSession(false):创建一个非自动提交功能的SqlSession,需手动提交
-
openSession():同openSession(false)
(4)SqlSession接口
SqlSession接口对象用于执行持久化操作。一个SqlSession对应着一次数据库会话,一次会话以SqlSession对象的创建开始,以SqlSession对象的关闭结束。
SqlSession接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其close()方法,将其关闭。再次需要会话,再次创建。SqlSession在方法内部创建,使用完毕后关闭。
二、创建工具类
(1)创建MyBatisUtil类
package com.lht.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisUtils {
private static SqlSessionFactory factory = null;
static {
String config = "mybatis.xml"; //需要和项目中的文件名一样
try {
InputStream in = Resources.getResourceAsStream(config);
//创建SqlSessionFactory对象,使用SqlSessionFactoryBuild
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession的方法
public static SqlSession getSqlSession(){
SqlSession sqlSession = null;
if( factory != null){
sqlSession = factory.openSession(); //非自动提交事务
}
return sqlSession;
}
}
(2)使用MyBatisUtil类
package com.lht;
import com.lht.domain.Student;
import com.lht.utils.MyBatisUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyApp2 {
public static void main(String[] args) throws IOException {
//获取SqlSession对象,从SqlSessionFactory中获取SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//指定要执行的sql语句的标识,sql映射文件中的namespace + "." + 标签的id值
String sqlId = "com.lht.dao.StudentDao.selectStudents";
//执行sql语句,通过sqlId找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
//输出结果
studentList.forEach( stu -> System.out.println(stu));
//9.关闭SqlSession对象
sqlSession.close();
}
}
三、MyBatis使用传统Dao开发方式
1. Dao开发
(1)创建Dao接口实现类
public class StudentDaoImpl implements StudentDao{}
(2)实现接口中select方法
@Override
public List<Student> selectStudents() {
//获取SqlSession对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
String sqlId = "com.lht.dao.StudentDao.selectStudents";
//执行sql语句,使用SqlSession类的方法
List<Student> students = sqlSession.selectList(sqlId);
//关闭
sqlSession.close();
return students;
}
测试查询操作:
MyBatisTest类中创建testSelectStudents方法
package com.lht;
import com.lht.dao.StudentDao;
import com.lht.dao.impl.StudentDaoImpl;
import com.lht.domain.Student;
import org.junit.Test;
import java.util.List;
public class TestMybatis {
@Test
public void testSelectStudents(){
StudentDao dao = new StudentDaoImpl();
List<Student> studentList = dao.selectStudents();
for(Student stu : studentList){
System.out.println(stu);
}
}
}
(3)实现接口中insert方法
public int insertStudent(Student student) {
SqlSession session = MyBatisUtil.getSqlSession();
int nums = session.insert("com.lht.dao.StudentDao.insertStudent",student);
session.commit();
session.close();
return nums;
}
测试insert
@Test
public void testInsert() throws IOException {
Student student = new Student();
student.setId(1006);
student.setName("林浩");
student.setEmail("linhao@163.com");
student.setAge(26);
int nums = studentDao.insertStudent(student);
System.out.println("使用Dao添加数据:"+nums);
}
(4)实现接口中update方法
public int updateStudent(Student student) {
SqlSession session = MyBatisUtil.getSqlSession();
int nums = session.insert("com.lht.dao.StudentDao.updateStudent",student);
session.commit();
session.close();
return nums;
}
测试update
@Test
public void testUpdate()throws IOException {
Student student = new Student();
student.setId(1006);
student.setAge(28);
int nums = studentDao.updateStudent(student);
System.out.println("使用Dao修改数据:"+nums);}
(5)实现接口中delete方法
public int deleteStudent(int id) {
SqlSession session = MyBatisUtil.getSqlSession();
int nums = session.insert("com.lht.dao.StudentDao.deleteStudent",1006);
session.commit();
session.close();
return nums;
}
测试delete
@Test
public void testDelete() throws IOException {
int nums = studentDao.
deleteStudent(1006);
System.out.println("使用Dao修改数据:"+nums);
}
2. 传统Dao开发方式的分析
在前面例子中自定义Dao接口实现类时发现一个问题:Dao的实现类其实并没有干什么实质性的工作,它仅仅就是通过SqlSession的相关API定位到映射文件mapper中相应id的SQL语句,真正对DB进行操作的工作其实是由框架通过mapper中的SQL完成的。
所以,MyBatis框架就抛开了Dao的实现类,直接定位到映射文件mapper中的相应SQL语句,对DB进行操作。这种对Dao的实现方式称为Mapper的动态代理方式。
Mapper动态代理方式无需程序员实现Dao接口。接口是由MyBatis结合映射文件自动生成的动态代理实现的。
动态代理原理
-
dao对象类型是StudentDao,全限定名称:
com.lht.dao.StudentDao
和namespace
是一样的。 -
方法名称:
selectStudents
,这个方法就是mapper
文件中的id
值selectStudents
-
通过
dao
中方法的返回值也可以确定MyBatis
要调用的SqlSession
的方法如果返回值是
list
,调用的是SqlSession.selectList()
方法如果返回值是
int
,或是非List
,看mapper
文件中的标签是<insert>,<update>
就会调用SqlSession
的insert,update
方法
四、MyBatis框架Dao代理
1. Dao代理实现CURD
(1)去掉Dao接口实现类
(2)getMapper获取代理对象
只需调用SqlSession的getMapper()方法,即可获取指定接口的实现类对象。该方法的参数为指定Dao接口类的class值。
SqlSession session = factory.openSession();
StudentDao dao = session.getMapper(StudentDao.class);
使用工具类
StudentDao studentDao = MyBatisUtil.getSqlSession().getMapper(StudentDao.class);
(3)使用Dao代理对象方法执行sql语句
select方法
@Test
public void testSelect() throws IOException {
final List<Student> studentList = studentDao.selectStudents();
studentList.forEach( stu -> System.out.println(stu));
}
insert方法
@Test
public void testInsert() throws IOException {
Student student = new Student();
student.setId(1006);
student.setName("林浩");
student.setEmail("linhao@163.com");
student.setAge(26);
int nums = studentDao.insertStudent(student);
System.out.println("使用Dao添加数据:"+nums);
}
update方法
@Test
public void testUpdate() throws IOException {
Student student = new Student();
student.setId(1006);
student.setAge(28);
int nums = studentDao.updateStudent(student);
System.out.println("使用Dao修改数据:"+nums);
}
delete方法
@Test
public void testDelete() throws IOException {
int nums = studentDao.deleteStudent(1006);
System.out.println("使用Dao修改数据:"+nums);
}
2. 深入理解参数
(1)parameterType
parameterType: 接口中方法参数的类型,类型的完全限定名或别名。
这个属性是可选的,因为MyBatis 可以推断出具体传入语句的参数,默认值为未设置(unset)。接口中方法的参数从java代码传入到mapper文件的sql语句。
别名或全限定名称:
int 或java.lang.Integer
hashmap 或java.util.HashMap
list 或java.util.ArrayList
student 或com.bjpowernode.domain.Student
<delete id="deleteStudent" parameterType="int">
delete from student where id=#{studentId}
</delete>
//等同于
<delete id="deleteStudent" parameterType="java.lang.Integer">
delete from student where id=#{studentId}
</delete>
(2)传递 一个简单参数
简单类型:mybatis把java的基本数据类型和String都较简单类型。
在mapper文件获取简单类型的一个参数的值,使用#{任意字符}
和方法的参数名无关。
接口方法
Student selectById(int id);
mapper文件
<select id="selectById" resultType="com.lht.domain.Student">
select id,name,email,age from student where id=#{studentId}
</select>
//#{studentId},studentId是自定义的变量名称,和方法参数名无关
测试方法
public void testSelectById(){
//一个参数
Student student = studentDao.selectById(1005);
System.out.println("查询id是1005的学生:"+student);
}
使用#{}之后,mybatis执行sql是使用的jdbc中的PreparedStatement对象
(MyBatis内部机制)由mybatis执行下面的代码:
1. mybatis创建Connection,PreparedStatement对象
String sql = "select * from student where id=?";
PreparedStatement pst = conn.preparedStatement(sql);
pst.setInt(1,1001);
2. 执行sql封装为resultType="com.lht.domain.Student"这个对象
ResultSet rs = ps.executeQuery();
Student studnet = null;
while(rs.next()){
//从数据库取表的一行数据,存到一个jdbc对象属性中
student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setEmail(rs.getString("email"));
student.setAge(rs.getInt("age"));
}
return student; //给了dao方法调用的返回值
(3)多个参数-使用@Param
当Dao
接口方法多个参数,需要通过名称使用参数。在方法形参前面加入@Param(“自定义参数名”)
,mapper
文件使用#{自定义参数名}
。
接口方法
List<Student> selectMulitParam(@Param("myname") String name,
@Param("myage") Integer age);
mapper文件
<select id="selectMultiParam" resultType="com.lht.domain.Student">
select * from studnet where name=#{myname} or age=#{myage}
</select>
测试方法
@Test
public void testSelectMultiParam(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> students = dao.selectMultiParam("里斯",20);
for(Student student : students){
System.out.println("学生="+ student);
}
sqlSession.close();
}
(4)多个参数-使用对象
使用java对象传递参数,java的属性值就是sql需要的参数值。每一个属性就是一个参数。语法格式:#{property,javaType=java中数据类型名,jdbcType=数据类型名称}
javaType, jdbcType的类型MyBatis可以检测出来,一般不需要设置。
常用格式#{property}
创建保存参数值的对象QueryParam
package com.lht.vo;
public class QueryParam {
private String paramName;
private Integer paramAge;
public String getParamName() {
return paramName;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public Integer getParamAge() {
return paramAge;
}
public void setParamAge(Integer paramAge) {
this.paramAge = paramAge;
}
}
接口方法
List<Student> selectMultiObject(QueryParam queryParam);
mapper文件
<select id="selectMultiObject" resultType="com.lht.domain.Student">
select * from student where name=#{paramName,javaType=java.lang.String,jdbcType=VARCHAR}
or age=#{paramAge,javaType=java.lang.Integer,jdbcType=INTEGER}
</select>
//或者
<select id="selectMutiObject" resultType="com.lht.domain.Student">
select * from student where name=#{paramName} or age=#{paramAge}
</select>
测试方法
@Test
public void testSelectMultiObject(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
QueryParam param = new QueryParam();
param.setParamName("张三");
param.setParamAge(24);
List<Student> students = dao.selectMultiObject(param);
for(Student student : students){
System.out.println("学生="+student);
}
sqlSession.close();
}
(5)多个参数-按位置
参数位置从0开始,引用参数语法#{arg位置},第一个参数是#{arg0}, 第二个是#{arg1}
注意:mybatis-3.3版本和之前的版本使用#{0},#{1}方式,从mybatis3.4开始使用#{arg0}方式。
接口方法
List<Student> selectByNameAndAge(String name,int age);
mapper文件
<select id="selectByNameAndAge" resultType="com.lht.doamin.Studnet">
select * from Studnet where name=#{arg0} or age=#{arg1}
</select>
测试方法
public void testSelectByNameAndAge(){
SqlSession sqlsession = MyBatisUtils.getSqlSession();
StudnetDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> stuList = studentDao.selectByNameAndAge("李四",20);
stuList.forEach( stu -> System.out.println(stu));
}
(6)多个参数-使用Map
Map
集合可以存储多个值,使用Map
向mapper
文件一次传入多个参数。Map
集合使用String
的key
,Object
类型的值存储参数。mapper
文件使用#{key}
引用参数值。
接口方法
List<Student> selectMultiMap(Map<String,Object> map);
mapper文件
<select id="selectMultiMap" resultType="com.lht.domain.Student">
select * from student where name=#{myname} or age=#{myage}
</select>
测试方法
public void testSelectMultiMap(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<String,Object> data = new Hashmap<>();
data.put("myname","王五");
data.put("myage",20);
List<Student> stuList = studentDao.selectMutltiMap(data);
stuList.foreach(stu -> System.out.println(stu));
}
3. #和$
#:占位符,告诉mybatis
使用实际的参数值代替。并使用PrepareStatement
对象执行sql
语句,#{...}
代替sql
语句的“?”
。这样做更安全,更迅速,通常也是首选做法。
mapper文件
<select id="selectById" resultType="com.lht.domain.Student">
select * from student where id=#{studentId}
</select>
转为MyBatis的执行是:
String sql=”select id,name,email,age from student where id=?”;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,1005);
解释
where id=?
就是where id=#{studentId}
ps.setInt(1,1005)
, 1005会替换掉#{studentId}
$:字符串替换,告诉mybatis
使用 $ 包含的“字符串”替换所在位置。使用Statement
把sql
语句和${}
的内容连接起来。主要用在替换表名,列名,不同列排序等操作。可能会造成安全性问题。
例子:
分别使用id,email列查询Student
接口方法
Student findById(int id);
Student findByEmail(String email);
mapper文件
<select id="findById" resultType="com.lht.domain.Student">
select * from student where id=#{studentId}
</select>
<select id="findByEmail" resultType="com.lht.domain.Student">
select * from student where email=#{studentEmail}
select>
测试方法
@Test
public void testFindStudent(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student stu1 = dao.findById(1002);
System.out.println("findById:"+ stu1);
Student stu2 = dao.findByEmail("lisi@qq.com");
System.out.println("findByEmail:"+stu2);
}
通用方法,使用不同列作为查询条件
接口方法
Student findByDiffField(@Param("col") String colunName,@Param("cval") Object value);
mapper文件
<select id="findByDiffField" resultType="com.lht.domain.Student">
select * from student where ${col} = #{cval}
</select>
测试方法
@Test
public void testFindDiffField(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student stu1 = dao.findByDiffField("id",1002);
System.out.println("按id列查询:"+stu1);
Student stu2 = dao.findByDiffField("email","lisi@qq.com");
System.out.println("按email查询:"+stu2);
}
五、封装Mybatis输出结果
1. resultType
执行 sql
得到 ResultSet
转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
resultType
结果类型,指sql
语句执行完毕后,数据转为java
对象(java类型是任意的)
处理方式:
- mybatis执行sql语句,然后mybatis调用类的无参构造方法,创建对象。
- mybatis把ResultSet指定列付给同名的属性
接口方法
ViewStudent selectStudentReturnViewStudent(@Param("sid") Integer id);
mapper文件
<select id="selectStudentReturnViewStudent" resultType="com.lht.vo.ViewStudent">
select id,name from student where id=#{sid}
</select>
测试方法
@Test
public void testeturnViewStudent(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
ViewStudent student = dao.selectStudentReturnViewStudent(1002);
System.out.println("1002 student:"+student);
}
resultType结果类型的值:
- 类型的全限定名称
- 类型的别名,例如
java.lang.Integer
别名是int
别名定义:
在主配置文件中
<!--定义别名-->
<typeAliases>
<!--第一种方式-->
<!--可以指定一个类型,一个自定义别名
type:自定义类型的全限定名称
alias:别名(容易记忆)
-->
<typeAlias type="com.lht.vo.ViewStudent" alias="vstu" />
<typeAlias type="com.lht.domain.Student" alias="stu" />
<!--第二种方式-->
<!--<package> name是包名,这个包中的所有类,类名就是别名(类名不区分大小写)-->
<package name="com.lht.domain"/>
<package name="com.lht.vo"/>
</typeAliases>
resultType的三种类型
A、简单类型
接口方法:
int countStudent();
mapper 文件:
<select id="countStudent" resultType="int">
select count(*) from student
</select>
测试方法:
@Test
public void testRetunInt(){
SqlSession sqlSession = MyBatisUtils.getSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int count = studentDao.countStudent();
System.out.println("学生总人数:"+ count);
}
B、 对象类型
接口方法:
Student selectById(int id);
mapper 文件:
<select id="selectById" resultType="Student">
select id,name,email,age from student where id=#{studentId}
</select>
测试方法
@Test
public void testSelectStudentById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = dao.selectStudentById(1002);
System.out.println("student=" + student);
}
C、 Map
sql
的查询结果作为 Map
的 key
和 value
。推荐使用 Map<Object,Object>
。
注意:Map
作为接口返回值,sql
语句的查询结果最多只能有一条记录。大于一条记录是错误。
接口方法:
Map<Object,Object> selectReturnMap(int id);
mapper 文件:
<select id="selectReturnMap" resultType="java.util.HashMap">
select name,email from student where id = #{studentId}
</select>
测试方法:
@Test
public void testReturnMap(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<Object,Object> retMap = studentDao.selectReturnMap(1002);
System.out.println("查询结果是 Map:"+retMap);
}
2. resultMap
resultMap
可以自定义 sql
的结果和 java
对象属性的映射关系。更灵活的把列值赋值给指定属性。
常用在列名和 java
对象属性名不一样的情况。
使用方式:
- 先定义 resultMap,指定列名和属性的对应关系。
- 在中把 resultType 替换为 resultMap。
接口方法:
List<Student> selectUseResultMap(QueryParam param);
mapper 文件:
<!-- 创建 resultMap
id:自定义的唯一名称,在<select>使用
type:期望转为的 java 对象的全限定名称或别名
-->
<resultMap id="studentMap" type="com.bjpowernode.domain.Student">
<!-- 主键字段使用 id -->
<id column="id" property="id" />
<!--非主键字段使用 result-->
<result column="name" property="name"/>
<result column="email" property="email" />
<result column="age" property="age" />
</resultMap>
<!--resultMap: resultMap 标签中的 id 属性值-->
<select id="selectUseResultMap" resultMap="studentMap">
select id,name,email,age from student where name=#{queryName} or
age=#{queryAge}
</select>
测试方法:
@Test
public void testSelectUseResultMap(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudnetDao dao = sqlSession.getMapper(StudentDao.class);
QueryParam param = new QueryParam();
param.setQueryName("李力");
param.setQueryAge(20);
List<Student> stuList = studentDao.selectUseResultMap(param);
stuList.forEach( stu -> System.out.println(stu));
}
如果还是在列明和属性名不一致的情况下,想使用resultMap可以在sql中给列起列别名。
<!--stuid,stuname,stuage是定义在MyStudent类中的属性名-->
<select id="selectDiffColProperty" result="com.lht.domain.MyStudent">
select id as stuid ,name as stuname, email as stuemail ,age as stuage from student
</select>
3. 模糊查询
模糊查询的实现有两种方式, 一是 java 代码中给查询数据加上“%” ; 二是在 mapper 文件 sql 语句的条件位置加上“%”
(1)在java代码中给查询数据加%
接口方法:
List<Student> selectLike1(String name);
mapper 文件:
<select id="selectLike1" resultType="com.lht.domain.Student">
select * from student where name like #{studentName}
</select>
测试方法:
@Test
public void testSelectLike1(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
String name = "%李%";
List<Student> stuList = dao.selectLike1(name);
for(Student stu : stuList){
System.out.println("#######学生="+stu);
}
}
(2)在mapper文件的sql语句的条件位置上加%
接口方法:
List<Student> selectLike2(String name);
mapper 文件:
<select id="selectLike2" resultType="com.lht.domain.Student">
select * from student where name like "%" #{studentName} "%"
</select>
测试方法:
@Test
public void testSelectLike2(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
String name = "李";
List<Student> stuList = dao.selectLike2(name);
for(Student stu : stuList){
System.out.println("#######学生="+stu);
}
}