MyBatis学习笔记
框架
三层架构
界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)
- 界面层(表示层,视图层): 主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和
用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。 - 业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
- 数据访问层: 与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交
给业务层,同时将业务层处理的数据保存到数据库 。
三层交互顺序
用户—> 界面层—>业务逻辑层—>数据访问层—>DB 数据库
三层架构优点
- 结构清晰、耦合度低, 各层分工明确。
- 可维护性高,可扩展性高。
- 有利于标准化。
- 开发人员可以只关注整个结构中的其中某一层的功能实现。
- 有利于各层逻辑的复用。
框架定义
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种认为,框架是可被应用开发者定制的应用骨架、模板。简单的说,框架其实是半成品软件,就是一组组件,供你使用完成你自己的系统。 从另一个角度来说框架一个舞台,你在舞台上做表演。在框架基础上加入你要完成的功能。框架安全的,可复用的,不断升级的软件。
框架解决的问题
框架要解决的最重要的一个问题是技术整合,在 JavaEE 的框架中,有着各种各样的技术,不同的应用,系统使用不同的技术解决问题。需要从 JavaEE 中选择不同的技术,而技术自身的复杂性,有导致更大的风险。企业在开发软件项目时,主要目的是解决业务问题。 即要求企业负责技术本身,又要求解决业务问题。这是大多数企业不能完成的。框架把相关的技术融合在一起,企业开发可以集中在业务领
域方面。另一个方面可以提供开发的效率。
常用框架(JavaEE常用)
MyBatis 框架:
MyBatis 是一个优秀的基于 java 的持久层框架,内部封装了 jdbc,开发者只需要关注 sql 语句本身,而不需要处理加载驱动、创建连接、创建 statement、关闭连接,资源等繁杂的过程。MyBatis 通过 xml 或注解两种方式将要执行的各种 sql 语句配置起来,并通过 java 对象和 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java对象并返回。
Spring 框架:
Spring 框架为了解决软件开发的复杂性而创建的。 Spring 使用的是基本的 JavaBean 来完成以前非常复杂的企业级开发。 Spring 解决了业务对象,功能模块之间的耦合,不仅在 javase、web 中使用,大部分 Java 应用都可以从 Spring 中受益。Spring 是一个轻量级控制反转(LOC)和面向切面(AOP)的容器。
SpringMVC 框架:
Spring MVC 属于 SpringFrameWork 3.0 版本加入的一个模块, 为 Spring 框架提供了构建 Web应用程序的能力。 现在可以 Spring 框架提供的 SpringMVC 模块实现 web 应用开发, 在 web 项目中可以无缝使用 Spring 和 Spring MVC 框架。
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>
- 根元素主要包括:
- 定义别名
- 数据源
- Mapper文件
- 根元素主要包括:
<dataSource>标签
原理
Mybatis 中访问数据库,可以连接池技术,但它采用的是自己的连接池技术。在 Mybatis 的 mybatis.xml配置文件中,通过<dataSource type=”pooled” >来实现 Mybatis 中连接池的配置。
从上图看出 Mybatis 将数据源分为三类:
数据源 | 说明 |
---|---|
UNPOOLED | 不使用连接池的数据源 |
POOLED | 使用连接池的数据源 |
JNDI | 使用 JNDI 实现的数据源 |
其中 UNPOOLED ,POOLED 数据源实现了 javax.sq.DataSource 接口, JNDI 和前面两个实现方式不同,了解就可以。
配置
MyBatis 在初始化时,根据<dataSource>的 type 属性来创建相应类型的的数据源 DataSource,即:
- type=” POOLED”: MyBatis 会创建 PooledDataSource 实例。
- type=” UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例。
- type=” JNDI”: MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用。
<dataSource type="POOLED">
<!--连接数据库的四个要素-->
<property name="driver" value=""/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</dataSource>
事务
提交事务机制
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的 Connection
对象的 commit()、rollback()方法来进行事务的提交与回滚。
<transactionManager type="JDBC" />
以上标签用于指定 MyBatis 所使用的事务管理器。 MyBatis 支持两种事务管理器类型: JDBC 与 MANAGED。
JDBC:使用 JDBC 的事务管理机制。即,通过 Connection 的 commit()方法提交,通过 rollback()方法回滚。但默认情况下, MyBatis 将自动提交功能关闭了,改为了手动提交。即程序中需要显式的对事务进行提交或回滚。从日志的输出信息中可以看到。
MANAGED:由容器来管理事务的整个生命周期(如 Spring 容器)。
自动/手动提交事务
可以使用factory的openSession()方法控制是否为开启事务。
- openSession(true)自动提交
- openSession(false)手动提交
数据库属性配置文件
为了方便对数据库连接的管理, DB 连接四要素数据一般都是存放在一个专门的属性文件中的。 MyBatis主配置文件需要从这个属性文件中读取这些数据。
在resources资源文件中创建xxxx.properties配置文件,并在配置文件中写入jdbc的相关属性。
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/databaseName?charset=utf-8 jdbc.username=username jdbc.password=password
在主配置文件中配置资源文件
在根目录下创建标签<properties>标签。<configuration> <properties resource="xxxx.properties" /> </configuration>
在主配置相关的指定值。
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <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> </environment> </environments>
typeAliases(全限定别名)
Mybatis 支持默认别名,我们也可以采用自定义别名方式来开发,主要使用在<select resultType=”别名”>mybatis.xml 主配置文件定义别名。
<typeAliases>
<!--
定义单个类型的别名
type:类型的全限定名称
alias:自定义别名
-->
<typeAlias type="com.bjpowernode.domain.Student" alias="别名"/>
<!--
批量定义别名,扫描整个包下的类,别名为类名(首字母大写或小写都可以)
name:包名
-->
<package name="com.bjpowernode.domain"/>
<package name="...其他包"/>
</typeAliases>
映射(Mapper)文件可以使用别名表示
<select id="selectStudents" resultType="别名">
select * from tableName
</select>
Mapper(映射器)
使用相对于类路径的资源,从 classpath 路径查找文件(区分大小写)。
<mapper resource="com/xrebirth/dao/MapperFileName.xml" />
指定包下的所有Dao接口,注意:此种方法要求 Dao 接口名称和 mapper 映射文件名称相同,且在同一个目录中。
<package name="com.xrebirth.dao"/
入门MaBatis框架(idea+maven)
环境搭建
- MYSQL
- IDEA
创建maven工程
- 根据项目类型使用原型创建不同的项目(这里使用的是JavaSE项目演示)
配置pom文件添加依赖
- 添加jdbc依赖
- 添加MyBatis框架依赖
- 不过滤main包中的配置文件
创建entity(实体类包)和dao(数据访问包)
- 在entity中创建Student实体类
private Integer id;
private String name;
private Integer age;
private String email;
在dao包中创建StudentDao接口
List<Student> selectQuery();
在dao包中创建创建StudentDao.xmlMapper映射文件(配置文件名称要与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.xrebirth.dao.StudentDao">
<!--
resultType:要与所要接收对象的实体类对象一致
-->
<!--查询语句-->
<select id="selectDemo" resultType="com.xrebirth.entity.Student">
select * from student where id = #{id};
</select>
<!--插入语句-->
<insert id="InsertDate">
insert into student (name, age, email) values (#{name},#{age},#{email});
</insert>
</mapper>
在resources资源文件中创建MyBatis主配置文件(添加jdbc相关配置)
<?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>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--配置mybatis环境-->
<environments default="mysql">
<!--id:数据源名称-->
<environment id="mysql">
<!--type:配置事务类型(使用jdbc事务)使用Connection的提交和回滚-->
<transactionManager type="JDBC"/>
<!--
dataSource:创建数据库Connection对象
type:POOLED 使用数据库连接池
-->
<dataSource type="POOLED">
<!--连接数据库四要素-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/exercise_02"/>
<property name="username" value="root"/>
<property name="password" value="963936892"/>
</dataSource>
</environment>
</environments>3
<mappers>
<!--告诉mybatis要执行sql语句的位置-->
<mapper resource="com/xrebirth/dao/DemoMapper.xml"/>
</mappers>
</configuration>
创建utils包创建MySqlUtil类
public class MyBatisUtil {
private static SqlSessionFactory factory = null;
static {
String config = "mybatis.xml";
try {
InputStream in = Resources.getResourceAsStream(config);
//创建SqlSessionFactory对象,使用
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取sqlSession对象
* @return SqlSession对象
*/
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
if (factory!=null) {
sqlSession = factory.openSession();
}
return sqlSession;
}
}
创建测试类
public class TestMybatis {
@Test
public void testSelect() {
//1.【重要】从SqlSessionFactory中获取SqlSession对象
SqlSession sqlSession = getSqlSession();
//2.【重要】指定要执行的sql语句标识。sql映射文件中的namespace+"."+标签的id值
String sqlId = "com.xrebirth.dao.StudentDao" + "." + "selectDemo";
//3.通过sqlId找到语句,执行sql语句
Student student = new Student(1, null, null, null);
List<Student> studentsList = sqlSession.selectList(sqlId, student);
//4.输出结果
studentsList.forEach(stu -> System.out.println(stu));
//5.关闭SqlSesson对象
sqlSession.close();
System.out.println("----------查询结束----------");
}
@Test
public void testInsert() {
//1.【重要】从SqlSessionFactory中获取SqlSession对象
SqlSession sqlSession = getSqlSession();
//2.【重要】指定要执行的sql语句标识。sql映射文件中的namespace+"."+标签的id值
String sqlId = "com.xrebirth.dao.StudentDao" + "." + "InsertDate";
//3.创建要插入的学生对象数据
Student student = new Student(null,"曹洋",11,"v.fysr");
//4.通过sqlId找到语句,执行sql语句
int nums = sqlSession.insert(sqlId,student);
//5.判断结果
if (nums != 0) {
System.out.println("插入成功,成功插入"+nums+"条");
} else {
System.out.println("插入失败");
}
//6.mybatis默认不是自动提交事务,所以需要在insert、update、delete后手动提交事务
sqlSession.commit();
//7.关闭sqlSession对象
sqlSession.close();
System.out.println("----------插入结束----------");
}
}
MyBatis使用传统Dao开发方式
- 使用 Dao 的实现类,操作数据库
Dao接口
public interface StudentDao {
//查询操作
List<Student> selectStudents();
//插入操作
int insertStudents(Student student);
}
创建Dao接口实现类
public class StudentDaoImpl implements StudentDao {
@Override
public List<Student> selectStudents() {
//获取SqlSession对象
SqlSession sqlSession = getSqlSession();
//获取sqlId
String sqlId = "com.xrebirth.dao.StudentDao.selectStudents";
//执行sql语句
List<Student> studentsList = sqlSession.selectList(sqlId);
//关闭SqlSession对象
sqlSession.close();
//返回结果集
return studentsList;
}
@Override
public int insertStudents(Student student) {
//获取SqlSession对象
SqlSession sqlSession = getSqlSession();
//获取sqlId
String sqlId = "com.xrebirth.dao.StudentDao.insertStudents";
//提交事务
sqlSession.commit();
//执行sql语句
return sqlSession.insert(sqlId, student);
}
}
创建测试类
public class TestMyBatis {
@Test
public void testSelect() {
StudentDao studentDao = new StudentDaoImpl();
List<Student> studentList = studentDao.selectStudents();
studentList.forEach(student -> System.out.println(student));
}
@Test
public void testInsert() {
StudentDao studentDao = new StudentDaoImpl();
Student student = new Student(null, "孔刚", "k.juzevzb@jlefmiipvu.tv", 20);
int result = studentDao.insertStudents(student);
System.out.println(result != 0 ? "插入成功,共插入:" + result : "插入失败");
}
}
动态代理(总结)
MyBatis动态代理:
List<Student> studentList = dao.selectStudents();
- dao对象类型是StudentDao,全限定名称是:com.xrebirth.dao.StudentDao(全限定名称和namespace是一样的)
- 方法名:selectStudents和namespace是一样的
- 通过dao中方法返回值也可以确定MyBatis要调用的SqlSession的方法
如果返回值是List则调用的是SqlSession.selectList()方法
如果返回值是int或者不是List、则看mapper文件中的标签是<insert>、<update>、<delete>就会调用SqlSession中的insert、update、dalete相关的方法 - 总结:通过以上第一点和第二点可以定位到接口和方法名,通过第三点可以推测出SQL执行的相应命令,以上三个条件都具备就可以使用MyBatis动态代理进行相关方法的增强。
Dao动态代理实现CURD
实现接口中的方法
传统 Dao 开发方式的分析
在前面例子中自定义 Dao 接口实现类时发现一个问题: Dao 的实现类其实并没有干什么实质性的工作,它仅仅就是通过 SqlSession 的相关 API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
所以, MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对DB 进行操作。这种对 Dao 的实现方式称为 Mapper 的动态代理方式。Mapper 动态代理方式无需程序员实现 Dao 接口。接口是由 MyBatis 结合映射文件自动生成的动态代
理实现的。
使用getMapper获取代理对象
只需调用 SqlSession 的 getMapper()方法,即可获取指定接口的实现类对象。该方法的参数为指定 Dao接口类的 class 值。
public class testStudent {
@Test
public void testSelect() {
//获取SqlSession对象
SqlSession sqlSession = getSqlSession();
//通过动态代理获取StudentDao中的对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);
//通过dao对象调用selectStudent()方法
List<Student> studentList = dao.selectStudent();
//通过dao对象调用insertStudent()方法
Student student = new Student(null, "于敏", 32, "w.yegdxzltlp@dmimevk.net");
int result = dao.insertStudent(student);
//通过dao对象调用updateStudent()方法
Student student1 = new Student(3, "彭刚", 26, null);
int result2 = dao.updateStudent(student1);
//通过dao对象调用deleteStudent()方法
Student student2 = new Student(2, null, null, null);
int result3 = dao.deleteStudent(student2);
//提交事务
sqlSession.commit();
System.out.println(result != 0 ? "成功插入:" + result + "条" : "插入失败");
System.out.println(result2 != 0 ? "成功修改:" + result + "条" : "修改失败");
System.out.println(result2 != 0 ? "成功删除:" + result + "条" : "删除失败");
//遍历输出
studentList.forEach((str)->System.out.println(str));
//关闭sqlSession对象
sqlSession.close();
}
}
注意:Mapper配置文件中的sql语句id要与接口方法名称相同。
定义别名
typeAlias
类型别名是为Java类型设置一个短的名字,可以方便我们引用某个类。
<!--定义别名-->
<typeAliases>
<!--第一种方式:
可以指定一个类型一个自定义别名
type:自定义类型的全限定名称
alias:别名(短小,容易记忆)
-->
<typeAlias type="com.xrebirth.entity.StudentStu" alias="stu" />
</typeAliases>
注意:以上两种方式是在主配置文件中定义别名。
package
类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简单类名小写。
<typeAliases>
<!--第二种方式:
<package>
name是包名,这个包中的所有类的类名就是别名(类名不区分大小写)
-->
<package name="com.xrebirth.entity"/>
</typeAliases>
Alias
在该类创建时,使用@Alias注解为其指定一个别名。
// Alias定义别名
@Alias("stu")
public class StudentStu {}
值得注意的是,MyBatis已经为许多常见的Java类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。
映射别名
别名 | 映射类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
map | Map/HashMap |
参数传递
parameterType
接口中方法参数的类型, 类型的完全限定名或别名。这个属性是可选的,因为 MyBatis可以推断出具体传入语句的参数,默认值为未设置( unset)。 接口中方法的参数从 java 代码传入到mapper 文件的sql语句。里面的参数类型可以写全限定名称也可以写MyBatis别名类型,例如:
<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>
MyBatis传递参数
Dao 接口中方法的参数只有一个简单类型(java 基本类型和 String),占位符 #{ 任意字符 },和方法的参数名无关。
单个参数传递
例如:
接口:
//单个参数传递
Student selectStudentId(Integer id);
Mapper:
<!--单个参数传递-->
<select id="selectStudentId" resultType="com.xrebirth.entity.Student">
select * from student where id = #{id}
</select>
<!--#{studentId} , studentId 是自定义的变量名称,和方法参数名无关-->
测试:
/**
* 单个参数传递
*/
@Test
public void testSelectStudentId() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = dao.selectStudentId(1);
System.out.println("id为1的学生对象为:"+student);
sqlSession.close();
}
多个参数传递-使用@Param
当 Dao 接口方法多个参数,需要通过名称使用参数。 在方法形参前面加入@Param(“自定义参数名”),mapper 文件使用#{自定义参数名}。
例如:
接口:
//多个参数传递:命名参数在形参定义的前面加入@Param("自定义参数名称")
Student selectMultiParam(@Param("name") String name, @Param("age") Integer age);
Mapper:
<!--多个参数传递-->
<select id="selectMultiParam" resultType="com.xrebirth.entity.Student">
select * from student where name = #{name} and age = #{age};
</select>
测试:
/**
* 多个参数传递
*/
@Test
public void testSelectMultiParam() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = dao.selectMultiParam("张三", 0);
if (student==null) {
System.out.println("姓名:张三,年龄20的查询为空");
}else {
System.out.println("姓名:张三,年龄20的查询结果为:"+student);
}
sqlSession.close();
}
多个参数传递-使用对象
使用规范版开发
例如:
接口:
//多个参数的传递-使用对象传参
List<Student> selectMultiObject(Student student);
Mapper:
<!--
多个参数的传递-使用java对象的属性值,作为参数实际值
使用对象语法:#{对象属性名,javaType=类型名称(全限定名称),jdbcType=数据类型}
javaType:指java中的属性数据类型
jdbcType:指数据库中的数据类型
例如:#{paramName,javaType=java.lang.String,jdbcType=VARCHAR}
以上方法在实际操作中很少用到,实际开发中会用简化版
-->
<select id="selectMultiObject" resultType="com.xrebirth.entity.Student">
select * from student where name = #{name,javaType=java.lang.String,jdbcType=VARCHAR} or age = #{age,javaType=java.lang.Integer,jdbcType=INTEGER}
</select>
测试:
/**
* 多个参数的传递-使用对象传参
*/
@Test
public void testSelectMultiObject() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student stu = new Student(null, "张三", 11, null);
List studentsList = dao.selectMultiObject(stu);
studentsList.forEach((stuItem)->System.out.println("姓名:张三或年龄11的查询结果为:"+stuItem));
sqlSession.close();
}
使用简化版开发
例如:
接口:
//多个参数的传递-使用对象传参
List<Student> selectMultiObject(Student student);
Mapper:
<!--
多个参数的传递-使用java对象的属性值,作为参数实际值(简化版)
MyBatis会通过反射机制获取到响应的属性的属性类型,所以不需要写对象类型和jdbc对象类型
-->
<select id="selectMultiObject" resultType="com.xrebirth.entity.Student">
select * from student where name = #{name} or age = #{age};
</select>
测试:
/**
* 多个参数的传递-使用对象传参
*/
@Test
public void testSelectMultiObject() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student stu = new Student(null, "张三", 11, null);
List<Student> studentsList = dao.selectMultiObject(stu);
studentsList.forEach((stuItem)->System.out.println("姓名:张三或年龄11的查询结果为:"+stuItem));
sqlSession.close();
}
多个参数传递-按位置(了解)
参数位置从 0 开始, 引用参数语法 #{ param 位置 } , 第一个参数是#{param1}, 第二个是#{param2}(param起始位置为1不是0)
注意: mybatis-3.3 版本和之前的版本使用#{0},#{1}方式, 从 mybatis3.4 开始使用#{param1}方式。
例如:
接口:
//多个参数的传递-按位置
List<Student> selectMultiPosition(String name,Integer age);
Mapper:
<!--
多个参数的传递-按位置
注意: mybatis-3.3 版本和之前的版本使用#{0},#{1}方式, 从 mybatis3.4 开始使用#{param1}方式。
-->
<select id="selectMultiPosition" resultType="com.xrebirth.entity.Student">
select * from student where name = #{param1} or age = #{param2}
</select>
测试:
/**
* 多个参数的传递-按位置
*/
@Test
public void testSelectMultiPosition() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> studentList2 = dao.selectMultiPosition("张三", 33);
studentList2.forEach((stu3)->System.out.println("姓名:张三或年龄33的查询结果为:"+stu3));
sqlSession.close();
}
多个参数传递-Map传递(了解)
Map 集合可以存储多个值, 使用Map向 mapper 文件一次传入多个参数。Map 集合使用 String的 key,Object 类型的值存储参数。 mapper 文件使用 # { key } 引用参数值。
例如:
接口:
//多个参数的传递-Map传递
List<Student> selectMultiMap(Map map);
Mapper:
<!--
多个参数的传递-Map传递
-->
<select id="selectMultiMap" resultType="com.xrebirth.entity.Student">
select * from student where name = #{name} or age = #{age}
</select>
测试:
/**
* 多个参数的传递-Map传递
*/
@Test
public void testSelectMultiMap() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map map = new HashMap();
map.put("name", "张三");
map.put("age", 30);
List<Student> studentsList3 = dao.selectMultiMap(map);
studentsList3.forEach(stu -> System.out.println("姓名:张三或年龄30的查询结果为:"+stu));
}
#和$
#占位符
- 告诉 mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。 这样做更安全,更迅速,通常也是首选做法。
Mapper:
<!--#占位符-->
<select id="selectStudentId" resultType="com.xrebirth.entity.Student">
select * from student where id = #{id}
</select>
转为MyBatis执行的是:
String sql=”select * from student where id = ?”;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,2);
说明:
where id=?就是where id=#{id}
ps.setInt(1,2) , 2会替换掉#{id}
$字符串
- 告诉 mybatis 使用$包含的“字符串”替换所在位置。使用 Statement 把 sql 语句和${}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。
Mapper:
<!--$占位符-->
<select id="selectStudentId" resultType="com.xrebirth.entity.Student">
select * from student where id = ${id}
</select>
转换为MyBatis执行的是:
String sql="select id,name, email,age from student where id=" + "2";
使用的Statement对象执行sql,效率比PreparedStatement低。
说明:
Statement对象使用的是字符串连接进行sql语句执行。
#与$区别:
1. #使用 ?在sql语句中做站位的, 使用PreparedStatement执行sql,效率高。
2. #能够避免sql注入,更安全。
3. $不使用占位符,是字符串连接方式,使用Statement对象执行sql,效率低。
4. $有sql注入的风险,缺乏安全性。
5. $可以替换表名或者列名。
封装MyBatis输出结果
resultType
执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。 resultType 和 resultMap,不能同时使用。
Mapper:
简单类型传递
接口:
//传递简单类型
int selectCount();
Mapper:
<!--传递简单类型-->
<select id="selectCount" resultType="Integer">
select count(*) from student
</select>
测试:
/**
* 传递简单类型
*/
@Test
public void testSelectCount() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int countNum = dao.selectCount();
System.out.println("简单类型查询结果" + countNum + "条");
}
对象类型
接口:
//传递对象类型
List<Student> selectStudents();
Mapper:
<!--传递对象类型-->
<select id="selectStudents" resultType="com.xrebirth.entity.Student">
select * from student
</select>
测试:
/**
* 传递对象类型
*/
@Test
public void testSelectStudents() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> studentsList = dao.selectStudents();
studentsList.forEach(student -> System.out.println("对象类型查询结果:"+student));
}
框架处理步骤:
- 使用构造方法创建该对象
- 调用set + 列名的对应的方法,将查询到的结果赋值给该对象
- 返回指定的返回的集合类型
Map
SQL的查询结果作为 Map 的 key 和 value。推荐使用 Map<Object,Object>。
注意: Map 作为接口返回值,SQL语句的查询结果最多只能有一条记录, 大于一条记录会产生错误。
接口:
//传递Map类型
Map<Object,Object> selectMap(Integer id);
Mapper:
<!--传递Map类型-->
<select id="selectMap" resultType="java.util.HashMap">
select * from student where id = #{id}
</select>
测试:
/**
* 传递Map类型
*/
@Test
public void testSelectMap() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<Object, Object> studentMap = dao.selectMap(1);
System.out.println("Map类型查询结果"+studentMap);
}
resultMap
resultMap可以对MySQL和entity实体类中的字段名不相同进行手动对接。
接口:
//resultMap
List<StudentStu> selectCustomStuStudent(@Param("name") String name,@Param("age") Integer age);
Mapper:
<!--
创建resultMap
id:自定义唯一名称,在<select>使用
type:期望转为的java对象的全限定名称或别名
column:数据库中的字段名
property:实体类中对应的字段名
-->
<resultMap id="selectCustomStuStudent" type="com.xrebirth.entity.StudentStu">
<!--主键字段使用id标签-->
<id column="id" property="stuId" />
<!--非主键字段使用result标签-->
<result column="name" property="stuName" />
<result column="age" property="stuAge" />
<result column="email" property="stuEmail" />
</resultMap>
<select id="selectCustomStuStudent" resultMap="selectCustomStuStudent">
select * from student where name = #{name} and age = #{age}
</select>
测试:
/**
* resultMap
*/
@Test
public void testSelectCustomStuStudent() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<StudentStu> studentStus = dao.selectCustomStuStudent("张三",20);
studentStus.forEach(stu -> System.out.println("resultMap实体类查询结果:"+studentStus));
}
like模糊查询
模糊查询的实现有两种方式, 一是 java 代码中给查询数据加上“%” ; 二是在 Mapper 文件 sql 语句的条件位置加上”%”
查询语句加%
例如:
接口:
//selectLikeFirst
List<Student> selectLikeFirst(@Param("name") String name);
Mapper:
<!--like模糊查询
1.查询语句加%
-->
<select id="selectLikeFirst" resultType="com.xrebirth.entity.Student">
select * from student where name like #{name}
</select>
测试:
/**
* likeFirst查询方式
*/
@Test
public void testSelectLikeFirst() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> studentList = dao.selectLike1("%张%");
studentList.forEach(stu -> System.out.println("模糊查询:"+stu));
}
Mapper文件加%
例如:
接口:
//selectLikeSecond
List<Student> selectLikeSecond(@Param("name") String name);
Mapper:
<!--like模糊查询
2.sql语句加%
-->
<select id="selectLikeSecond" resultType="com.xrebirth.entity.Student">
select * from student where name like '%' #{name} '%'
</select>
测试:
/**
* likeSecond查询方式
*/
@Test
public void testSelectLikeSecond() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> students = dao.selectLikeSecond("三");
students.forEach(stu -> System.out.println("模糊查询Second:"+stu));
}
动态SQL语句
动态 SQL,通过 MyBatis 提供的各种标签对条件作出判断以实现动态拼接 SQL 语句。 这里的条件判断使用的表达式为 OGNL 表达式。 常用的动态 SQL 标签有
注意:在 mapper 的动态 SQL 中若出现大于号(>)、小于号(<)、大于等于号(>=),小于等于号(<=)等符号,最好将其转换为实体符号。否则, XML 可能会出现解析出错问题。 特别是对于小于号(<),在 XML 中是绝不能出现的。否则解析 mapper 文件会出错。
实体符号表
符号 | 实体符号 | 说明 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
>= | >= | 大于等于 |
<= | <= | 小于等于 |
动态 SQL —<if>
对于该标签的执行,当 test 的值为 true 时,会将其包含的 SQL 片断拼接到其所在的 SQL 语句中。
语法: <if test=”条件”> sql语句的部分 </if>
接口:
//动态sql-if
List<Student> selectIf(Student student);
Mapper:
<!--动态sql-if-->
<select id="selectIf" resultType="com.xrebirth.entity.Student">
select * from student where id > 0
<if test="name!=null and name != ''">
and name = #{name}
</if>
<if test="age > 0">
or age > #{age}
</if>
</select>
测试:
/**
* 动态sql-if
*/
@Test
public void testSelectIf() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setName("张三");
student.setAge(20);
List<Student> students = dao.selectIf(student);
students.forEach(stu -> System.out.println("if:"+stu));
}
动态 SQL —<where>
<if/>标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1 的子句。因为,若 where 后的所有<if/>条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where, SQL出错。所以,在 where 后,需要添加永为真子句 1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。使用<where/>标签, 在有查询条件时, 可以自动添加上 where 子句;没有查询条件时,不会添加where 子句。需要注意的是,第一个<if/>标签中的 SQL 片断,可以不包含 and。不过,写上 and 也不错,系统会将多出的 and 去掉。但其它<if/>中 SQL 片断的 and,必须要求写上。否则 SQL 语句将拼接出错。
语法: <where> 其他动态 sql </where>
接口:
<!--动态sql-where-->
<select id="selectWhere" resultType="com.xrebirth.entity.Student">
select * from student
<where>
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age > 0">
or age > #{age}
</if>
</where>
</select>
Mapper:
//动态sql-where
List<Student> selectWhere(Student student);
测试:
/**
* 动态sql-where
*/
@Test
public void testSelectWhere() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
//student.setName("张三");
student.setAge(20);
List<Student> students = dao.selectWhere(student);
students.forEach(stu -> System.out.println("where:"+stu));
}
动态 SQL —<foreach>
<foreach/>标签用于实现对于数组与集合的遍历。对其使用,需要注意:
- collection 表示要遍历的集合类型, list , array 等。
- open、 close、 separator 为对遍历内容的 SQL 拼接。
语法:
接口:
//动态sql-foreachOne
List<Student> selectForeachOne(List<Student> studentList);
Mapper:
<!--动态sql-foreachOne-->
<select id="selectForeachOne" resultType="com.xrebirth.entity.Student">
select * from student where id in
<foreach collection="list" item="myid" open="(" close=")" separator=",">
#{myid.id}
</foreach>
</select>
测试:
/**
* 动态sql-foreachOne
*/
@Test
public void testForEach() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
List<Student> studentList = new ArrayList<>();
Student stu1 = new Student(3, null, null, null);
Student stu2 = new Student(6, null, null, null);
studentList.add(stu1);
studentList.add(stu2);
List<Student> students = dao.selectForeachOne(studentList);
students.forEach(stu3 -> System.out.println("foreachOne:"+stu3));
}
动态 SQL—代码片段
<sql/>标签用于定义 SQL 片断,以便其它 SQL 标签复用。而其它标签使用该 SQL 片断,需要使用<include/>子标签。该<sql/>标签可以定义 SQL 语句中的任何部分,所以<include/>子标签可以放在动态 SQL的任何位置。
接口:
//代码片段-selectSql01
List<Student> selectSql();
Mapper:
<!--代码复用-先定义后使用-->
<sql id="StudentSQL01">
select * from student
</sql>
<select id="selectSql" resultType="com.xrebirth.entity.Student">
<!--引用代码片段-->
<include refid="StudentSQL01" />
</select>
测试:
/**
* 代码片段
*/
@Test
public void testSelectSql() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> students = dao.selectSql();
students.forEach(stu -> System.out.println("代码片段:"+stu));
}
扩展-PageHelper(分页数据处理)
通过PageHelper插件获取数据库数据并自动处理分页数据。项目地址:https://github.com/pagehelper/Mybatis-PageHelper
- Maven坐标
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
- 在<environments>之前加入
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
- PageHelper 对象
查询语句之前调用 PageHelper.startPage 静态方法。除了 PageHelper.startPage 方法外,还提供了类似用法的 PageHelper.offsetPage 方法,在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个 MyBatis 查询方法会被进行分页。
/**
* pagehelperLimit
*/
@Test
public void testPagehelperLimit() {
SqlSession sqlSession = getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
//获取第二页到第三页的内容
PageHelper.startPage(2,3);
List<Student> students = dao.selectSql();
students.forEach(stu-> System.out.println("pagehelperLimit:"+stu));
}
附录
关于sttingszh中的相关属性设置。
<!-- settings是 MyBatis 中全局的调整设置,它们会改变 MyBatis 的运行时行为,应谨慎设置 -->
<settings>
<!-- 该配置影响的所有映射器中配置的缓存的全局开关。默认值true -->
<setting name="cacheEnabled" value="true"/>
<!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。默认值false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否允许单一语句返回多结果集(需要兼容驱动)。 默认值true -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。默认值true -->
<setting name="useColumnLabel" value="true"/>
<!-- 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 默认值false -->
<setting name="useGeneratedKeys" value="false"/>
<!-- 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 -->
<!-- 默认值PARTIAL -->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<!-- 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。默认SIMPLE -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 设置超时时间,它决定驱动等待数据库响应的秒数。 -->
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<!-- 允许在嵌套语句中使用分页(RowBounds)默认值False -->
<setting name="safeRowBoundsEnabled" value="false"/>
<!-- 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 默认false -->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 -->
<setting name="localCacheScope" value="SESSION"/>
<!-- 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 -->
<setting name="jdbcTypeForNull" value="OTHER"/>
<!-- 指定哪个对象的方法触发一次延迟加载。 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>