MyBatis学习笔记
MyBatis学习笔记
框架概述
常用结构
三层架构
三层框架包含三层
- 界面层 - User Interface Layer
- 业务逻辑层 - Business Logic Layer
- 数据访问层 - Data Access Layer
界面层
主要功能是接收用户的数据,显示请求的处理结果,使用 web 页面实现与用户的交互。手机 app也是表示层,用户在 app中操作,业务逻辑在服务器端处理
业务逻辑层
接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据
数据访问层
与数据库打交道,主要实现对数据的增删改查,将存储在数据库中的数据提交给业务层,同时将业务层的数据保存到数据库
三层的处理请求的交互
用户 -> 界面层 -> 业务逻辑层 -> 数据访问层 -> DB数据库
使用三层结构的好处
- 结构清晰、耦合度低、各层次分工明确
- 可维护性高,可扩展性高
- 有利于标准化编程
- 开发人员可以仅关注整个结构中的某一层的功能实现
- 有利于各层逻辑的复用
常用框架
MyBatis框架
- MyBatis是一个优秀的基于 java 的持久层框架,内部封装了 JDBC
- 开发者只需要关注 SQL 语句本身为不需要处理加载驱动、创建链接、创建Statement等繁杂的过程。
- MyBatis通过 xml或注解两种方式将要执行的各种 SQL语句配置起来,
- 通过 java对象和 SQL的动态参数进行映射生成最终执行的 SQL语句
- 最后由 MyBatis框架执行 SQL并将结果映射为 java对象并返回
Spring框架
- Spring是为了解决软件开发的复杂性而创建的,使用基本的 JavaBean来完成以前非常复杂的企业级开发
- 解决了业务对象,功能模块之间的耦合性问题,可在 javaweb,javase等多种场景下使用
- Spring是一个轻量级的 控制反转(IoC) 和 面向切面(AOP) 的容器
SpringMVC框架
- SpringMVC是SpringFrameWork3.0版本加入的一个模块,提供了 Spring 框架构建 Web 应用程序的能力
- 可以在 Spring框架提供的SpringMVC模块中实现 web应用开发
框架解释
框架定义
框架是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法,也可以被认为是应用开发者定制的应用骨架、模板
简单来说,框架就是半成品软件,就是一组组件,供开发者完成自己的系统
总结:框架是安全的、可复用的、不断升级的开发模板
框架解决的问题
框架要解决的最重要的一个问题就是技术整合,在 J2EE 的框架中,有着各种各样的技术,不同的系统使用不同的技术解决问题。因此需要从 J2EE中选择不同的技术,而技术自身的复杂性又导致了更大的风险。
企业在开发软件项目时,主要目的是解决业务问题,既要求企业负责技术本身,又要求解决业务问题。这是大多数企业不能完成的,而框架把相关的技术融合到了一起,使得企业开发可以集中在业务领域方面。
JDBC
前情回顾
public void findStudent() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 注册 MySQL驱动
Class.forName("com.mysql.jdbc.Driver");
// 链接数据库的基本信息
String url = "jdbc:mysql://localhost:3306/springdb";
String username = "root";
String password = "zhao";
// 创建连接对象
conn = DriverManager.getConnection(url, username, password);
// 保存查询结果
List<Student> stuList = new ArrayList<>();
// 创建Statement,用来执行 SQL语句
stmt = conn.createStatement();
// 执行查询创建记录集
rs = stmt.executeQuery("select * from student");
while(rs.next()) {
Student stu = new Student();
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
// 从数据库取出数据转为Student对象,封装到List集合中
stuList.add(stu);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭资源
if(rs != null)
rs.close();
if(stmt != null)
stmt.close();
if(conn != null)
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
}
}
JDBC的缺陷
- 代码比较多,开发效率低
- 需要关注 Connection, Statement, ReslutSet对象的创建和销毁
- 对 ResultSet查询的结果,需要自己封装
- 重复的代码比较多
- 业务代码和数据库操作混在一起
MyBatis概述
MyBatis再次介绍
- MyBatis 本是 Apache 的一个开源项目 iBatis,2010年这个项目由 Apache Software Foundation迁移到了 Google Code,并改名为 MyBatis,2013年迁移到 GitHub
- iBatis 一词来源于 “Internet”和 “Abatis”的组合,是一个基于 Java 的持久层框架
能解决的问题
- 解决使用JDBC的复杂性,不用编写重复的创建Connection, Statement语句
- 不用编写关闭资源代码,可以直接使用 Java代码,表示结果数据
- 开发者专注于 SQL 的处理
快速入门
案例入门
1)下载MyBatis
链接地址:https://github.com/mybatis/mybatis-3/releases
2)创建数据库和表
CREATE TABLE `student` (
`id` int(11) NOT NULL ,
`name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3)创建 maven工程
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
4)编写实体类
package com.kuang.domain;
public class Student() {
// 属性名和列名一样
private Integer id;
private String name;
private String email;
private Integer age;
// set, get, toString方法
}
5)编写 Dao接口 StudentDao
public interface StudentDao {
List<Student> selectStudnets();
}
6)创建映射文件 StudentDao.xml
要求:
- 在dao包中创建映射文件
- 映射文件名和接口名一样
<?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:必须有值,自定义的唯一字符串
推荐使用:dao 接口的全限定名称
-->
<mapper namespace="com.bjpowernode.dao.StudentDao">
<!--
<select>: 查询数据, 标签中必须是 select 语句
id: sql 语句的自定义名称,推荐使用 dao 接口中方法名称,
使用名称表示要执行的 sql 语句
resultType: 查询语句的返回结果数据类型,使用全限定类名
-->
<select id="selectStudents" resultType="com.bjpowernode.domain.Student">
<!--要执行的 sql 语句-->
select id,name,email,age from student
</select>
</mapper>
7)创建主配置文件
在 src/main 下创建 resources 目录,设置 resources 目录为 resources root并在该目录下出创建主配置文件:mybatis.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>
<!--配置 mybatis 环境-->
<environments default="mysql">
<!--id:数据源的名称-->
<environment id="mysql">
<!--配置事务类型:使用 JDBC 事务(使用 Connection 的提交和回滚)-->
<transactionManager type="JDBC"/>
<!--
数据源 dataSource:创建数据库 Connection 对象
type: POOLED 使用数据库的连接池
-->
<dataSource type="POOLED">
<!--连接数据库的四个要素-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--告诉 mybatis 要执行的 sql 语句的位置-->
<mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
</mappers>
</configuration>
8)创建测试类
@Test
public void testStart() throws IOException {
//1.mybatis 主配置文件
String config = "mybatis-config.xml";
//2.读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//3.创建 SqlSessionFactory 对象,目的是获取 SqlSession
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//4.获取 SqlSession,SqlSession 能执行 sql 语句
SqlSession session = factory.openSession();
//5.执行 SqlSession 的 selectList()
List<Student> studentList = session.selectList("com.bjpowernode.dao.StudentDao.selectStudents");
//6.循环输出查询结果
studentList.forEach( student -> System.out.println(student));
//7.关闭 SqlSession,释放资源
session.close();
}
9)配置日志功能
在 mybatis.xml文件中下如如下配置
<settings> <setting name="logImpl" value="STDOUT_LOGGING" /></settings>
CRUD
Insert
1)在 StudentDao 接口中增加方法
int insertStudent(Student studnet);
2)StudentDao.xml 中加入 SQL语句
<insert id="insertStudent"> insert into student(id,name,email,age) values(#{id},#{name},#{email},#{age})</insert>
3)增加测试方法
@Testpublic void testInsert() throws IOException { //1.mybatis 主配置文件 String config = "mybatis-config.xml"; //2.读取配置文件 InputStream in = Resources.getResourceAsStream(config); //3.创建 SqlSessionFactory 对象 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //4.获取 SqlSession SqlSession session = factory.openSession(); //5.创建保存数据的对象 Student student = new Student(); student.setId(1005); student.setName("张丽"); student.setEmail("zhangli@163.com"); student.setAge(20); //6.执行插入 insert int rows = session.insert( "com.bjpowernode.dao.StudentDao.insertStudent",student); //7.提交事务 session.commit(); System.out.println("增加记录的行数:"+rows); //8.关闭 SqlSession session.close();}
Update
1)StudentDao接口中增加方法
int updateStudent(Student student);
2)在 StudentDao.xml 中增加 SQL语句
<update id="updateStudent"> update student set age = #{age} where id=#{id}</update>
3)增加测试方法
@Testpublic void testUpdate() throws IOException { //1.mybatis 主配置文件 String config = "mybatis-config.xml"; //2.读取配置文件 InputStream in = Resources.getResourceAsStream(config); //3.创建 SqlSessionFactory 对象 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //4.获取 SqlSession SqlSession session = factory.openSession(); //5.创建保存数据的对象 Student student = new Student(); student.setId(1005);//要修改的 id student.setAge(30); //要修改的年龄值 //6.执行更新 update int rows = session.update( "com.bjpowernode.dao.StudentDao.updateStudent",student); //7.提交事务 session.commit(); System.out.println("修改记录的行数:"+rows); //8.关闭 SqlSession session.close();}
Delete
1)StudentDao接口中增加方法
int deleteStudent(int id);
2)StudentDao.xml中增加 SQL语句
<delete id="deleteStudent"> delete from student where id=#{studentId}</delete>
3)增加测试方法
@Testpublic void testDelete() throws IOException { //1.mybatis 主配置文件 String config = "mybatis-config.xml"; //2.读取配置文件 InputStream in = Resources.getResourceAsStream(config); //3.创建 SqlSessionFactory 对象 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //4.获取 SqlSession SqlSession session = factory.openSession(); //5.删除的 id int id = 1001; //6.执行删除 delete int rows = session.delete( "com.bjpowernode.dao.StudentDao.deleteStudent",id); //7.提交事务 session.commit(); System.out.println("修改记录的行数:"+rows); //8.关闭 SqlSession session.close();}
对象分析
Resources类
Resources类,用于读取资源文件,其中有很多方法通过加载并解析资源文件,返回不同类型的 IO流对象
SqlSessionFactoryBuilder类
SqlSessionFactory的创建需要使用 SqlSessionFactoryBuilder 对象的 build() 方法。SqlSessionFactoryBuilder 对象在创建完工厂对象之后就完成了历史使命,可被销毁,所以一般会将 SqlSessionFactoryBuilder 对象创建为一个方法内的局部对象。方法结束,对象销毁。
SqlSessionFactory接口
SqlSessionFactory接口对象是一个重量级对象(系统开销大的对象),是线程安全的。一个应用只需要一个该对象即可,创建 SqlSession需要使用 SqlSessionFactory接口的 openSession()方法
- openSession(true):创建一个有自动提交功能的 SqlSession
- openSession(false):创建一个必须要手动提交的 SqlSession
- openSession():同 openSession(false)
SqlSession接口
SqlSession接口对象用于执行持久化操作,一个 SqlSession对应着一次数据库会话,一次会话以 SqlSession对象的创建开始,以 SqlSession对象的关闭结束。
SqlSession接口对象是线程不安全的,所以每次数据库会话结束前需要马上调用其 close()方法。再次需要会话时,再次创建。SqlSession在方法内部创建,使用完毕后关闭。
自定义工具类
创建 MyBatisUtil类
package com.bjpowernode.common;/*** <p>Description: 实体类 </p>* <p>Company: http://www.bjpowernode.com*/public class MyBatisUtil { //定义 SqlSessionFactory private static SqlSessionFactory factory = null; static { //使用 静态块 创建一次 SqlSessionFactory try{ String config = "mybatis-config.xml"; //读取配置文件 InputStream in = Resources.getResourceAsStream(config); //创建 SqlSessionFactory 对象 factory = new SqlSessionFactoryBuilder().build(in); }catch (Exception e){ factory = null; e.printStackTrace(); } } /* 获取 SqlSession 对象 */ public static SqlSession getSqlSession(){ SqlSession session = null; if( factory != null){ session = factory.openSession(); } return session; }}
使用 MyBatisUtil类
@Testpublic void testUtils() throws IOException { SqlSession session = MyBatisUtil.getSqlSession(); List<Student> studentList = session.selectList( "com.bjpowernode.dao.StudentDao.selectStudents"); studentList.forEach( student -> System.out.println(student)); session.close();}
Dao开发
1)创建 Dao接口实现类并实现其方法
public class StudentDaoImpl implements StudentDao { public List<Student> selectStudents() { SqlSession session = MyBatisUtil.getSqlSession(); List<Student> studentList = session.selectList("com.bjpowernode.dao.StudentDao.selectStudents"); session.close(); return studentList; } public int insertStudent(Student student) { SqlSession session = MyBatisUtil.getSqlSession(); int nums = session.insert("com.bjpowernode.dao.StudentDao.insertStudent",student); session.commit(); session.close(); return nums; } public int updateStudent(Student student) { SqlSession session = MyBatisUtil.getSqlSession(); int nums = session.insert("com.bjpowernode.dao.StudentDao.updateStudent",student); session.commit(); session.close(); return nums; } public int deleteStudent(int id) { SqlSession session = MyBatisUtil.getSqlSession(); int nums = session.insert("com.bjpowernode.dao.StudentDao.deleteStudent",1006); session.commit(); session.close(); return nums; }}
2)创建测试类并测试方法
public class MyBatisTest { StudentDao studentDao = new StudentDaoImpl(); @Test public void testSelect() throws IOException { final List<Student> studentList = studentDao.selectStudents(); studentList.forEach( stu -> System.out.println(stu)); } @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); } @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); } @Test public void testDelete() throws IOException { int nums = studentDao.deleteStudent(1006); System.out.println("使用 Dao 修改数据:"+nums); }}
传统 Dao 开发方式的分析
- Dao的实现类并没有干什么实质性的工作,仅仅是通过 SqlSession 的相关 API 定位到映射文件 mapper 中相应 id的SQL语句,真正对DB进行的操作是由框架通过 mapper中的 SQL完成的
- 所以MyBatis框架就抛开了 Dao的实现类,直接定位到了映射文件 mapper中的相应的 SQL语句,称为 Mapper的动态代理方式
- Mapper动态代理方式无需程序员实现 Dao接口,接口是由 MyBatis结合映射文件自动生成的动态代理实现的
Dao代理
代理实现CRUD
1)通过 getMapper() 获取代理对象
只需要调用 SqlSession 的 getMapper()方法,即可获取指定接口的实现类对象。该方法的参数为指定 Dao接口类的 class值
// 常规方式SqlSession session = factory.openSession();StudentDao dao = session.getMapper(StudentDao.class);// 使用工具类StudentDao dao = MyBatisUtil.getSqlSession().getMapper(StudentDao.class);
2)使用 Dao代理对象方法执行 SQL语句
// select 方法:@Testpublic void testSelect() throws IOException { final List<Student> studentList = studentDao.selectStudents(); studentList.forEach( stu -> System.out.println(stu));}// insert 方法:@Testpublic 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 方法@Testpublic 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 方法@Testpublic void testDelete() throws IOException { int nums = studentDao.deleteStudent(1006); System.out.println("使用 Dao 修改数据:"+nums);}
原理
动态代理
深入理解参数
parameterType
- parameterType:接口中方法参数的类型,类型的完全限定名或别名。
- 这个属性是可选的,因为MyBatis可以推断出具体传入语句的参数,默认值为未设置(unset)
- 接口中方法的参数从 java 代码传入到 mapper 文件的 sql 语句
- <select>, <insert>, <update>, <delete>都可以使用 parameterType 指定类型
<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>
只有一个简单参数情况
Dao 接口中方法的参数只有一个简单类型(java基本类型和 String),占位符 #{ 任意字符 }和方法中的参数名无关
// 接口方法
Student selectById(int id);
<!-- mapper文件-->
<select id="selectById" resultType="com.bjpowernode.domain.Student">
select id, name, email, age
from student
where id=#{studentId}
</select>
<!--
#{studentId},studentId是自定义的变量名称,和方法参数名无关
-->
// 测试方法
@Test
public void testSelectById() {
// 1个参数的情况
Student stu = studentDao.selectById(1005);
System.out.println("查询id是1005的学生:" + stu);
}
多个参数 - 使用 @Param
当 Dao接口方法中有多个参数时,需要通过名称使用参数。在方法形参前面加入 @Param(“自定义参数名”), mapper文件使用 #{自定义参数名}
// 接口方法List<Studnet> selectMultiParam(@Param("personName") String name, @Param("ersonAge") int age);
<!-- mapper文件--><select id="selectMultiParam" resultType="com.bjpowernode.domain.Student"> select id, name, email, age from studnet where name = #{personName} or age = #{personAge}</select>
// 测试方法@Testpublic void testMultiParam() { List<Studnet> stuList = studentDao.selectMultiParam("李四", 20); stuList.forEach(stu -> System.out.println(stu))l}
多个参数 - 使用对象
使用 java对象传递参数,java的属性值就是 SQL需要的参数值。每一个属性就是一个参数
语法格式:#{property, javaType=java中的数据类型名, jdbcType=表中的数据类型名}
- javaType, jdbcType的类型 MyBatis可以自动检测出来,一般不需要设置,所以常用格式也就成了 #{property}
// 创建保存参数值的对象public class QueryParam { private String quertName; private int queryAge; // set, get方法}
// 接口方法List<Student> selectMultiObject(QueryParam queryParam);
<!-- mapper文件 --><select id="selectMultiObject" resultType="studnet"> select id, name, email, age from student where name=#{queryName} or age=#{queryAge}</select><!-- 或者 --><select id="selectMultiObject" resultType="studnet"> select id, name, email, age from student where name=#{queryName, javaType=string, jdbcType=VARCHAR} or age=#{queryAge, javaType=int, jdbcType=INTEGER}</select>
// 测试方法@Testpublic void selectMultiObject(){ QueryParam qp = new QueryParam(); qp.setQueryName("李力"); qp.setQueryAge(20); List<Student> stuList = studentDao.selectMultiObject(qp); stuList.forEach( stu -> System.out.println(stu));}
多个参数 - 按位置
参数位置从 0 开始,引用参数语法 #{arg位置},第一个参数是 #{arg0}, 第二个参数是 #{arg1}
注意:mybatis-3.3版本及之前使用 #{0}, #{1}方式,mybatis-3.4版本及之后使用 #{arg0}方式
// 接口方法List<Student> selectByNameAndAge(String name, int age);
<!-- mapper文件 --><select id="selectByNameAndAge" resultType="studnet"> select id, name, email, age from student where name=#{arg0} or age=#{arg1}</select>
// 测试方法@Testpublic void selectMultiObject(){ // 按位置参数 List<Student> stuList = studentDao.selectByNameAndAge("李四", 20); stuList.forEach(stu -> System.out.println(stu));}
多个参数 - 使用 Map
Map集合可以存储多个值,使用 Map 可以向 mapper文件一次传入多个参数。
Map集合使用String类型的 key,Object类型的值存储参数。mapper文件使用 #{key}引用参数
// 接口方法List<Student> selectMultiMap(Map<String, Object> map);
<!-- mapper文件 --><select id="selectMultiMap" resultType="student"> select id, name, email, age from student where name=#{myname} or age=#{myage}</select>
// 测试方法@Testpublic void testSelectMultiMap() { Map<String, Object> data = new HashMap<>; data.put("myname", "李四"); data.put("myage", 20); List<Student>stuList = studentDao.selectMultiMap(data); stuList.forEach(stu -> System.out.println(stu));}
# 和 $ 的比较
#:占位符
告诉 MyBatis 使用实际的参数值代替,并使用 PreparedStatement对象执行 SQL语句,#{…}代替了 SQL语句中的“?”。
这样做更安全、迅速,通常也是首选的做法
<!-- mapper文件 --><select id="selectById" resultType="studnet"> select id, name, email, age 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}*/
$:字符串替换
告诉 MyBaits使用 $ 包含的 “字符串”替换所在位置。使用 Statement 把 SQL 语句和 ${}的内容连接起来。
主要用在替换表名、列名,不同列排序等操作
例1:分别使用 id,email 查询 Student
// 接口方法Student findById(int id);Studnet findByEmail(String email);
<!-- mapper文件 --><select id="findById" resultType="com.bjpowernode.domain.Student"> select * from student where id=#{studentId}</select><select id="findByEmail" resultType="com.bjpowernode.domain.Student"> select * from student where email=#{stuentEmail}</select>
// 测试方法@Testpublic void testFindStuent(){ Student student1 = studentDao.findById(1002); System.out.println("findById:"+student1); Student student2 = studentDao.findByEmail("zhou@126.net"); System.out.println("findByEmail:"+student2);}
例2:通用方法,使用不同列作为查询条件
// 接口方法Studnet findByDifferField(@Param("col")String colunName, @Param("cval")Object value);
<!-- mapper文件 --><select id="findByDiffField" resultType="com.bjpowernode.domain.Student"> select * from student where ${col} = #{cval}</select>
// 测试方法@Testpublic void testFindDiffField(){ Student student1 = studentDao.findByDiffField("id",1002); System.out.println("按 id 列查询:"+student1); Student student2 = studentDao.findByDiffField("email","zhou@126.net"); System.out.println("按 email 列查询:"+student2);}
封装输出结果
resultType
执行 SQL 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。
注意:如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
resultType 和 resultMap不能同时使用
1)简单类型
// 接口方法int countStudent();
<!-- mapper文件 --><select id="countStudent" resultType="int"> select count(*) from student</select>
// 测试方法@Testpublic void testReturnInt() { int count = studentDao.countStudent(); System.out.println("学生总人数:" + count);}
2)对象类型
// 接口方法Student selectById(int id);
<!-- mapper文件 --><select id="selectById" resultType="student"> select id, name, email, age from student where id=#{studentId}</select>
框架的处理:使用构造方法创建对象,调用 setXxx()给属性赋值
Student stu = new Student();
注意:Dao接口方法返回是集合类型,需要指定集合中的类型,不是集合本身
3)Map
SQL的查询结果作为 Map的 Key和 value,推荐使用 Map<Object, Object>
注意:Map作为接口返回值,SQL语句的查询结果最多只能有一条记录
// 接口方法Map<Object, Object> selectReturnMap(int id);
<!-- mapper文件 --><select id="selectReturnMap" resultType="hashMap"> select name,email from student where id = #{studentId}</select>
// 测试方法@Testpublic void testReturnMap(){ Map<Object,Object> retMap = studentDao.selectReturnMap(1002); System.out.println("查询结果是 Map:"+retMap);}
resultMap
resultMap 可以自定义 SQL的结果和 Java对象属性的映射关系。更灵活的把列值赋值给指定属性。
常用在列名和java对象属性名不一样的情况
使用方式
- 先定义 resultMap,指定列名和属性的对应关系
- 在 <select>中把 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>
// 测试方法@Testpublic void testSelectUseResultMap(){ QueryParam param = new QueryParam(); param.setQueryName("李力"); param.setQueryAge(20); List<Student> stuList = studentDao.selectUseResultMap(param); stuList.forEach( stu -> System.out.println(stu));}
实体类属性名和列名不同的处理方式
1)使用列别名和 <resultType>
// 创建新的实体类public class PrimaryStudent { private Integer stuId; private String stuName; private Integer stuAge; // set , get 方法}
// 接口方法List<PrimaryStudent> selectUseFieldAlias(QueryParam param);
<!-- mapper文件 --><select id="selectUseFieldAlias" resultType="com.bjpowernode.domain.PrimaryStudent"> select id as stuId, name as stuName,age as stuAge from student where name=#{queryName} or age=#{queryAge}</select>
// 测试方法@Testpublic void testSelectUseFieldAlias(){ QueryParam param = new QueryParam(); param.setQueryName("李力"); param.setQueryAge(20); List<PrimaryStudent> stuList; stuList = studentDao.selectUseFieldAlias(param); stuList.forEach( stu -> System.out.println(stu));}
2)使用 <resultMap>
// 接口方法List<PrimaryStudent> selectUseDiffResultMap(QueryParam param);
<!-- mapper文件 --><!-- 创建 resultMap id:自定义的唯一名称,在<select>使用 type:期望转为的 java 对象的全限定名称或别名--><resultMap id="primaryStudentMap" type="com.bjpowernode.domain.PrimaryStudent"> <!-- 主键字段使用 id --> <id column="id" property="stuId" /> <!--非主键字段使用 result--> <result column="name" property="stuName"/> <result column="age" property="stuAge" /></resultMap><!--resultMap: resultMap 标签中的 id 属性值--><select id="selectUseDiffResultMap" resultMap="primaryStudentMap"> select id,name,email,age from student where name=#{queryName} or age=#{queryAge}</select>
// 测试方法@Testpublic void testSelectUseDiffResultMap(){ QueryParam param = new QueryParam(); param.setQueryName("李力"); param.setQueryAge(20); List<PrimaryStudent> stuList; stuList = studentDao.selectUseDiffResultMap(param); stuList.forEach( stu -> System.out.println(stu));}
模糊查询
模糊查询的实现有两种方式:
- 在 java代码中给查询数据加上 “%”
- 在 mapper文件 SQL语句的条件位置加上 “%”
1)例1:java代码中提供 “%”
// 接口方法List<Student> selectLikeFirst(String name);
<!-- mapper文件 --><select id="selectLikeFirst" resultType="com.bjpowernode.domain.Student"> select id,name,email,age from student where name like #{studentName}</select>
// 测试方法:@Testpublic void testSelectLikeOne(){ String name="%力%"; List<Student> stuList = studentDao.selectLikeFirst(name); stuList.forEach( stu -> System.out.println(stu));}
2)mapper文件中使用 like
// 接口方法List<Student> selectLikeSecond(String name);
<!-- mapper文件 --><select id="selectLikeSecond" resultType="com.bjpowernode.domain.Student"> select id,name,email,age from student where name like "%" #{studentName} "%"</select>
// 测试方法@Testpublic void testSelectLikeSecond(){ String name="力"; List<Student> stuList = studentDao.selectLikeSecond(name); stuList.forEach( stu -> System.out.println(stu));}
动态SQL
动态 SQL,指的是通过 MyBatis 提供的各种标签对条件作出判断以实现动态拼接 SQL语句的效果。
这里的条件判断使用 OGNL表达式,常用的动态 SQL标签有 <if>, <where>, <choose>, <foreach>
动态 SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的条件进行查询。提交的查询条件不同,执行的 SQL语句不同。
注意:在 mapper 的动态 SQL中若出现大于号、小于号、大于等于号等符号,最好将其转换为实体符号。否则,XML可能会解析出错
符号 | 意思 | 代码 |
---|---|---|
< | 小于 | < |
> | 大于 | > |
>= | 大于等于 | >= |
<= | 小于等于 | <= |
<if>标签
对于该标签的执行,当 test的值为 true时,会将其包含的 SQL片段拼接到其所在的 SQL语句中。
语法:<if test=“条件”> SQL 语句 </if>
// 接口方法List<Student> selectStudentIf(Student student);
<!-- mapper文件 --><select id="selectStudentIf" resultType="com.bjpowernode.domain.Student"> select id,name,email,age from student where 1=1 <if test="name != null and name !='' "> and name = #{name} </if> <if test="age > 0 "> and age > #{age} </if></select>
// 测试方法@Testpublic void testSelect() throws IOException { Student param = new Student(); param.setName("李力"); param.setAge(18); List<Student> studentList = studentDao.selectStudentIf(param); studentList.forEach( stu -> System.out.println(stu));}
<where>标签
使用 <where>标签能使 SQL语句在存在查询条件时,自动添加上 where子句;没有查询条件时,不会添加 where子句
第一个 <if>标签中 可以包含 and,也可以不包含,系统会将多余的 and去掉。但其它<if>中必须写上 and,否则拼接会出错
语法:<where>其他动态 SQL<where>
// 接口方法List<Student> selectStudentWhere(Student student);
<!-- mapper文件 --><select id="selectStudentWhere" resultType="com.bjpowernode.domain.Student"> select id,name,email,age from student <where> <if test="name != null and name !='' "> and name = #{name} </if> <if test="age > 0 "> and age > #{age} </if> </where></select>
// 测试方法@Testpublic void testSelectWhere() throws IOException { Student param = new Student(); param.setName("李力"); param.setAge(18); List<Student> studentList = studentDao.selectStudentWhere(param); studentList.forEach( stu -> System.out.println(stu));}
<foreach>标签
<foreach>标签用于实现对于数组和集合的遍历
- collection 表示要遍历的集合类型,list, array等
- open, close, separator 为遍历内容的 SQL拼接
语法:<foreach collection="集合类型”, open="开始的字符”, close="结束的字符”, item="集合中的成员”, separator="分隔符">#{item的值}</foreach>
遍历 List<简单类型>
表达式中的 List使用 list表示,其大小使用 list.size表示
// 接口方法List<Student> selectStudentForList(List<Integer> idList);
<!-- mapper文件 --><select id="selectStudentForList" resultType="com.bjpowernode.domain.Student"> select id,name,email,age from student <if test="list !=null and list.size > 0 "> where id in <foreach collection="list" open="(" close=")" item="stuid" separator=","> #{stuid} </foreach> </if></select>
// 测试方法@Testpublic void testSelectForList() { List<Integer> list = new ArrayList<>(); list.add(1002); list.add(1005); list.add(1006); List<Student> studentList = studentDao.selectStudentForList(list); studentList.forEach( stu -> System.out.println(stu));}
遍历 List<对象类型>
// 接口方法List<Student> selectStudentForList2(List<Student> stuList);
<!-- mapper文件 --><select id="selectStudentForList2" resultType="com.bjpowernode.domain.Student"> select id,name,email,age from student <if test="list !=null and list.size > 0 "> where id in <foreach collection="list" open="(" close=")" item="stuobject" separator=","> #{stuobject.id} </foreach> </if></select>
// 测试方法@Testpublic void testSelectForList2() { List<Student> list = new ArrayList<>(); Student s1 = new Student(); s1.setId(1002); list.add(s1); s1 = new Student(); s1.setId(1005); list.add(s1); List<Student> studentList = studentDao.selectStudentForList2(list); studentList.forEach( stu -> System.out.println(stu));}
<sql>标签
<sql>标签用于定义 SQL片段,以便其他 SQL标签的复用。而其他标签内部引入该 SQL片段时,需要通过 <include>子标签
<sql>标签可以定义 SQL语句中的任何部分,所以 <include>子标签可以放在动态 SQL的任何位置
// 接口方法List<Student> selectStudentSqlFragment(List<Student> stuList);
<!-- mapper文件 --><sql id="studentSql"> select id,name,email,age from student</sql><select id="selectStudentSqlFragment" resultType="com.bjpowernode.domain.Student"> <!-- 引用 sql 片段 --> <include refid="studentSql"/> <if test="list !=null and list.size > 0 "> where id in <foreach collection="list" open="(" close=")" item="stuobject" separator=","> #{stuobject.id} </foreach> </if></select>
// 测试方法@Testpublic void testSelectSqlFragment() { List<Student> list = new ArrayList<>(); Student s1 = new Student(); s1.setId(1002); list.add(s1); s1 = new Student(); s1.setId(1005); list.add(s1); List<Student> studentList = studentDao.selectStudentSqlFragment(list); studentList.forEach( stu -> System.out.println(stu));}
配置文件
主配置文件
主配置文件即核心配置文件,mybatis.xml
特点:
-
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></configuration>
-
包含内容:定义别名、数据源、mapper文件
dataSource标签
dataSource类型
MyBatis 中访问数据库可以通过连接池技术,通过 <dataSource type="pooled">来实现
- UNPOOLED:不使用连接池的数据源
- POOLED:使用连接池的数据源
- JNDI:使用 JNDI 实现的数据源
其中,UNPOOLED、POOLED 数据源实现了 javax.sql.DataSource 接口,JNDI 和前面两个实现方式不同。
dataSource配置
在 MyBatis.xml 文件中,如下配置 dataSource
<dataSource type="POOLED"> <!--连接数据库的四个要素--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ssm?charset=utf-8"/> <property name="username" value="root"/> <property name="password" value="123456"/></dataSource>
MyBatis.xml 在初始化时,会根据 <dataSource>标签中的 type属性来创建相应类型的数据源
- type = POOLED:会创建 PooledDataSource实例
- type = UNPOOLED:会创建 UnPooledDataSource实例
- type = JNDI:会从 JNDI服务上查找 DataSource实例,然后返回
事务处理
1)默认手动提交事务
MyBatis框架是对 JDBC的封装,所以 MyBatis框架的事务控制方式也是使用 JDBC的 Connection对象的 commit(), rollback()
Connection对象的 setAutoCommit() 方法来设置事务的提交方式
可使用 <transactionManager type=“xxx”>标签来指定事务管理器
- type = JDBC:使用 JDBC的事务管理机制,默认情况下自动提交功能关闭了,程序中需要显式地对事务进行提交和回滚
- type = MANAGED:由容器来管理事务的整个生命周期
2)自动提交事务
设置自动提交的方法为 SqlSessionFactory对象的 openSession()方法
- SqlSession openSession(boolean autoCommit);
参数为 true时,启用自动提交
空参或参数为 false时,需要手动提交
数据库属性配置
为了方便对数据库连接的管理,DB连接四要素数据一般都是存放在一个专门的属性文件中的
MyBatis主配置文件需要从这个属性文件中读取这些数据
1)在 classpath路径下创建 properties文件
在 resources目录下创建 jdbc.properties文件,名称可自定义
jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/ssm?charset=urf-8jdbc.username=rootjdbc.password=zhao
2)在核心配置文件中使用<properties>标签
修改著配置文件,在文件的开始位置处加入:
<?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="jdbc.properties"/></configuration>
3)使用 key指定值
<dataSource type="POOLED"> <!-- 使用properties文件,语法:${key} --> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/></dataSource>
类型别名
MyBatis支持默认别名和自定义别名,可在 <select resultType="别名">等标签中使用
<typeAliases> <!-- 定义单个类型的别名 type:类型的全限定名称 alias:自定义别名 --> <typeAlias type="com.bjpowernode.domain.Student" alias="mystudent"/> <!-- 批量定义别名,扫描整个包下的类,别名为类名(首字母大写或小写都可以) name:包名 --> <package name="com.bjpowernode.domain"/> <package name="...其他包"/></typeAliases>
映射器
1)<mapper resource=“”>
使用相对于类路径的资源,从 classpath路径查找文件
<mappers> <mapper resource="com/kuang/dao/StudentDao.xml" /></mappers>
2)<package name=“”/>
指定包名下的所有 Dao接口,该方法只有在 Dao接口名称和 mapper映射文件同名并且在同一个目录的情况下才可以使用
<mappers> <package name="com.kuang.dao"/></mappers>
扩展内容
分页工具
PageHelper是MyBatis的通用分页插件
1)pom依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
2)plugin配置
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
3)使用PageHelper对象
查询语句之前调用 PageHelper.startPage方法
@Test
public void testSelect() throws IOException {
//获取第 1 页,3 条内容
PageHelper.startPage(1,3);
List<Student> studentList = studentDao.selectStudents();
studentList.forEach( stu -> System.out.println(stu));
}