0.软件框架技术简介
软件框架(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也
指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
框架的功能类似于基础设施,与具体的软件应用无关,但是提供并实现最为基础的软件架构和体系。
-
为什么需要框架技术:
-
帮我们更快更好地构建程序
-
是一个应用程序的半成品
-
提供可重用的公共结构
-
按一定规则组织的一组组件
-
-
优势:
-
不用再考虑公共问题
-
专心在业务实现上
-
结构统一,易于学习、维护
-
新手也可写出好程序
-
不要重复造轮子(Stop Trying to Reinvent the Wheel),已经成为开发人员的基本原则。
Java世界中的主流框架技术: Spring、SpringMVC、MyBatis、Struts、Hibernate、SpringBoot等。
1.MyBatis简介
1.1.ORM思想的提出
数据库中数据是以表的形式存在的,而java中使用的数据都是对象型的。所以不得不要将表数据转换成对象数据。
这样就会产生大量的没有技术含量的纯 “体力” 型代码。
while(rs.next()) {
Business business = new Business();
business.setBusinessId(rs.getInt("businessId"));
business.setPassword(rs.getString("password"));
business.setBusinessName(rs.getString("businessName"));
business.setBusinessAddress(rs.getString("businessAddress"));
business.setBusinessExplain(rs.getString("businessExplain"));
business.setStarPrice(rs.getDouble("starPrice"));
business.setDeliveryPrice(rs.getDouble("deliveryPrice"));
list.add(business);
}
上面代码就属于纯 “体力” 型代码。这些代码工作量大、单调枯燥、占用大量开发时间。为了解决这个问题,出现了 ORM思想。
ORM(对象-关系映射):完成对象数据到关系型数据映射的机制称为对象-关系映射。
1.2.MyBatis框架
MyBatis就是一个ORM框架。当然,也是一个持久层框架。
MyBatis封装了JDBC, 将数据库中的表数据自动封装到对象中。这样就可以以面向对象的方式操作数据了。它的出
现,使得开发工作量变小了,可以将精力集中在业务逻辑的处理上。代码精简易读。
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google
code,并且改名为MyBatis 。 也就是说:iBatis3.0之后都要改名为MyBatis 。
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和
参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的
POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
实际上,MyBatis最核心的功能,就是实现了输入映射和输出映射。
2.MyBatis实例
2.1.添加Mybatis依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
2.2.创建MyBatis配置文件
在 resources 文件夹中创建 SqlMapConfig.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>
<!-- 配置数据源相关属性和事务 -->
<environments default="development">
<!-- 可以配置多个数据源环境,默认使用default中的值 -->
<environment id="development">
<!-- 使用jdbc的事务管理 -->
<transactionManager type="JDBC" />
<!-- 配置数据源,并使用自带数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="123" />
</dataSource>
</environment>
</environments>
<!-- 配置映射文件,可配置多个 -->
<mappers>
<mapper resource="com/neusoft/po/Emp.xml" />
</mappers>
</configuration>
-
transactionManager标签的type属性有两种取值:
-
JDBC:全部使用jdbc的事务管理
-
MANAGED:不使用事务管理,也从不提交
-
-
dataSource标签的type属性有三种取值:
-
POOLED:使用Mybatis自带的数据库连接池
-
UNPOOLED:不使用任何数据库连接池
-
JNDI:jndi形式使用数据库连接
-
2.3.创建po类
创建 Emp.java类
package com.neusoft.po;
public class Emp {
private Integer empno;
private String ename;
private String job;
private String hiredate;
private Double sal;
private Integer deptno;
@Override
public String toString() {
return empno+"\t"+ename+"\t"+job+"\t"+hiredate+"\t"+sal+"\t"+deptno;
}
//get/set方法...
}
注意:po类属性名必须与数据库表的字段名一致。
2.4.创建映射文件
在 po 类所在包下,创建相同名称的映射文件:Emp.xml
<?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="emp">
<select id="getEmpById" parameterType="int" resultType="com.neusoft.po.Emp">
select * from emp where empno = #{empno}
</select>
</mapper>
注意:
mapper标签:映射文件的根标签。
mapper标签namespace属性:命名空间,对sql进行分类管理,可防止id重复
select标签:表示查询。
select标签id属性:此属性要唯一。这个id可称为statement的id。
select标签parameterType属性:sql参数的类型。
select标签resultType属性:sql语句执行后返回的类型。
sql语句:#{}: 表示sql参数,一个占位符。
当parameterType属性为对象类型时:#{} 中的参数名为对象的属性名。
当parameterType属性为单个值时,参数名可以任意。
从映射文件中可以看到:MyBatis会根据字段名与实体对象中的属性名进行映射,从而实现自动将查询数据注入到
实体对象的每一个属性中。这就是输出映射。
2.5.测试
package com.neusoft;
import java.io.InputStream;
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 com.neusoft.po.Emp;
public class Test {
public static void main(String[] args) {
try {
//读取配置文件,获得配置文件信息
InputStream input = Resources.getResourceAsStream("SqlMapConfig.xml");
//通过配置信息创建SqlSessionFactory
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(input);
//通过SqlSessionFactory打开数据库会话
SqlSession sqlSession = ssf.openSession();
//通过SqlSession的selectOne(返回一行记录时使用)方法执行sql语句
Emp emp = sqlSession.selectOne("emp.getEmpById",7521);
System.out.println(emp);
//关闭数据库会话
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
当返回一条记录时,使用 SqlSession 的 selectOne 方法执行sql语句,MyBatis会将返回记录输出映射 为一个java对象。
当返回多条记录时,使用 SqlSession 的 selectList 方法执行sql语句,MyBatis会将返回记录输出映射为 一个java集合。
selectOne 方法和 selectList 方法都只有两个参数:
第一个参数为指定的sql语句,根据映射文件中的id,注意要加上命名空间
第二个参数是给 sql语句参数传值(可以是一个值、或一个对象)
2.6.MyBatis核心接口和类总结
-
SqlSessionFactoryBuilder负责构建SqlSessionFactory,并且提供了多个build()方法的重载。也就是说:此对象可以从xml配置文件,或从Configuration对象来构建SqlSessionFactory。
-
SqlSessionFactory就是创建SqlSession实例的工厂。通过openSession方法来获取SqlSession对象。而且, SqlSessionFactory一旦被创建,那么在整个应用程序期间都存在。
-
SqlSession是一个面向程序员的接口,它提供了面向数据库执行sql命令所需的所有方法。SqlSession对应一 次数据库会话,它是线程不安全的。
2.7.使用log4j输出日志
上面实例运行后,在控制台中只输出了查询结果,却没有中间的执行过程信息。这对我们开发时进行错误调试是非
常不利的。我们希望在开发时,能够看到详细的执行过程
MyBatis默认使用log4j输出日志。所以,下面使用log4j来让MyBatis输出更多的日志信息。
1.添加log4j依赖
<!-- 使用log4j输出更多的日志信息 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.在resources文件夹中添加log4j配置文件(log4j.properties)
下面是MyBatis官网提供的log4j配置文件内容
# 全局日志配置
log4j.rootLogger=debug, stdout
# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
3.封装持久层
MyBatis开发DAO层有两种方式:
-
原始dao方式
-
mapper代理方式
3.1.原始dao方式
按照JDBC课程中封装dao层的方式,我们可以先封装一个 Util 工具类,在此工具类中封装一个获取
SqlSessionFactory的方法。然后创建dao接口和实现类。
SqlSessionFactory工具类:
package com.neusoft.util;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Util {
public static SqlSessionFactory sqlSessionFactory = null;
public static SqlSessionFactory getSqlSessionFactory() {
if(sqlSessionFactory==null){
String resource = "mybatis/SqlMapConfig.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}
}
return sqlSessionFactory;
}
}
dao接口:
package com.neusoft.dao;
import java.util.List;
import com.neusoft.po.Emp;
public interface EmpDao {
public Emp getEmpById(int empno);
public List<Emp> listEmp();
}
dao实现类:
package com.neusoft.dao.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.neusoft.dao.EmpDao;
import com.neusoft.po.Emp;
import com.neusoft.util.Util;
public class EmpDaoImpl implements EmpDao{
@Override
public Emp getEmpById(int empno){
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
Emp emp = sqlSession.selectOne("emp.getEmpById",empno);
sqlSession.close();
return emp;
}
@Override
public List<Emp> listEmp(){
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
List<Emp> list = sqlSession.selectList("emp.listEmp");
sqlSession.close();
return list;
}
}
测试:
EmpDao dao = new EmpDaoImpl();
Emp emp = dao.getEmpById(7369);
System.out.println(emp);
List<Emp> list = dao.listEmp();
for(Emp emp : list) {
System.out.println(emp);
}
从上面代码中可以发现,使用原始dao方式存在很多问题:
-
dao实现类中存在大量重复代码
-
调用sqlSession方法时,将statement的id硬编码了
-
调用sqlSession方法时传入的参数,由于sqlSession使用了泛型,所以即使传入参数的数据类型错误,在编译阶段也不会报错。
3.2.mapper代理方式
程序员只需要mapper接口和mapper.xml映射文件,Mybatis可以自动生成mapper接口实现类代理对象。程序员
编写mapper接口需要遵循一些开发规范。
mapper代理方式开发规范:
-
映射文件中的 namespace 必须是 mapper 接口的地址。
-
映射文件中 statement 的 id 必须与 mapper 接口中的方法名一致。
-
映射文件中 parameterType 必须与 mapper 接口中的方法参数类型一致。
-
映射文件中 resultType 必须与 mapper 接口中的返回值类型一致。(实际上,代理对象就是根据返回值类型来判断是使用selectOne方法还是selectList方法)
3.2.1.创建mapper接口
package com.neusoft.mapper;
import java.util.List;
import com.neusoft.po.Emp;
public interface EmpMapper {
public Emp getEmpById(int empno);
public List<Emp> listEmpAll();
}
3.2.2.创建mapper映射文件
注意:mapper接口与映射文件最好在同一个文件夹内,并且接口名与映射文件名要一致
<?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.neusoft.mapper.EmpMapper">
<select id="getEmpById" parameterType="int" resultType="Emp">
select * from emp where empno = #{empno}
</select>
<select id="listEmpAll" resultType="Emp">
select * from emp order by empno
</select>
</mapper>
3.2.3.注册mapper映射文件
<configuration>
<mappers>
<mapper resource="com/neusoft/mapper/EmpMapper.xml" />
</mappers>
</configuration>
3.2.4.获取代理对象测试
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpById(7369);
System.out.println(emp);
List<Emp> list = mapper.listEmpAll();
for(Emp e : list) {
System.out.println(e);
}
3.3.MyBatis配置的优化
3.3.1.批量加载映射文件
如果映射文件与mapper接口名称一致,且处在同一个文件夹内,那么就可以使用接口来批量加载映射文件。
<configuration>
<mappers>
<package name="com.neusoft.mapper"/>
</mappers>
</configuration>
自动加载com.neusoft.mapper包下,所有与接口名称一致的映射文件。(package标签可写多个)
3.3.2.批量定义类别名
在MyBatis中的配置文件中, parameterType和resultType都需要指定自定义类的全路径。类的全路径一般都很长,所以需要进行优化。也就是给类定义别名。
<configuration>
<typeAliases>
<package name="com.neusoft.po"/>
</typeAliases>
</configuration>
自动扫描指定包中的类,并自动为这些类定义别名。定义的别名就是类名,而且首字母大写小写均可。
3.3.3.数据库配置优化
创建db.properties数据库配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123
在MyBatis配置文件中使用db.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="db.properties"></properties>
<!-- 定义类别名 -->
<typeAliases>
<package name="com.neusoft.po"/>
</typeAliases>
<!-- 配置数据源相关属性和事务 -->
<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>
<!-- 加载映射文件 -->
<mappers>
<package name="com.neusoft.mapper"/>
</mappers>
</configuration>
将有关数据库的配置分离出去,有利于分类管理。
4.常用数据库操作
4.1.多条件查询
<select id="listEmp" parameterType="Emp" resultType="Emp">
select *
from emp
where job like concat('%',#{job},'%') and deptno=#{deptno}
order by empno
</select>
Emp emp = new Emp();
emp.setJob("经");
emp.setDeptno(20);
List<Emp> list = mapper.listEmp(emp);
for(Emp e : list) {
System.out.println(e);
}
注意:
parameterType只有一个。所以,有多个参数时使用对象传值(这就是输入映射)。
{} 中书写的是实体对象的属性名,所以要严格区分大小写。
4.2.转义字符查询
由于 <(小于号)是标签关键词,因此不能识别小于号。所以MyBatis中设计了一些转义字符,来代替一些特殊字符:
<select id="listEmpBySal" parameterType="double" resultType="Emp">
select * from emp where sal < #{sal} order by empno
</select>
List<Emp> list = mapper.listEmpBySal(2000.0);
for(Emp e : list) {
System.out.println(e);
}
4.3.返回单值查询
<select id="listEmpCount" resultType="int">
select count(*) from emp
</select>
int count = mapper.listEmpCount();
System.out.println(count);
注意:只有返回一行一列,resultType才能使用基本数据类型
4.4.插入(不获取主键)
<insert id="insertEmp1" parameterType="Emp">
insert into emp(ename,job,hiredate,deptno)
values(#{ename},#{job},#{hiredate},#{deptno})
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp1(emp);
sqlSession.commit(); //注意:要commit提交
System.out.println(result);
注意:增删改都会返回int值,表示影响的行数。但是,insert标签中不能书写resultType属性
4.5.插入(获取主键)
<insert id="insertEmp2" parameterType="Emp">
<selectKey keyProperty="empno" resultType="int" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into emp(ename,job,hiredate,deptno)
values(#{ename},#{job},#{hiredate},#{deptno})
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp2(emp);
sqlSession.commit();
System.out.println(result);
System.out.println(emp.getEmpno()); //获取返回的主键
注意:
selectKey标签中的 select LAST_INSERT_ID() 语句就能获取生成的主键
selectKey标签中的keyProperty属性就是主键名,MyBatis会自动将获取的主键封装给此属性。
order的值有两种:BEFORE、AFTER
BEFORE:先获取主键,然后执行insert; 比如 Oracle数据库。
AFTER:先执行insert,然后获取主键; 比如 MySql数据库。
4.6.插入(获取主键)
<insert id="insertEmp3" parameterType="Emp" useGeneratedKeys="true" keyProperty="empno">
insert into emp(ename,job,hiredate,deptno)
values(#{ename},#{job},#{hiredate},#{deptno})
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp3(emp);
sqlSession.commit();
System.out.println(result);
System.out.println(emp.getEmpno()); //获取返回的主键
useGeneratedKeys设置为true后,mybatis会使用JDBC的getGeneratedkeys方法获取由数据库内部自动生成的主键,并将该值赋值给由keyProperty指定的属性; 注意:此种方式只适合于有自增长列的数据库(mysql、sqlserver等)
4.7.修改
<update id="updateEmp" parameterType="Emp">
update emp set job=#{job},sal=#{sal} where empno=#{empno}
</update>
Emp emp = new Emp();
emp.setEmpno(7934);
emp.setJob("经理");
emp.setSal(2000.0);
int result = mapper.updateEmp(emp);
sqlSession.commit();
System.out.println(result);
注意:增删改都会返回int值,表示影响的行数。但是,insert标签中不能书写resultType属性
4.8.删除
<delete id="deleteEmp" parameterType="int">
delete from emp where empno=#{empno}
</delete>
int result = mapper.deleteEmp(7939);
sqlSession.commit();
System.out.println(result);
5.动态sql
动态sql主要用于解决查询条件不确定的情况。也就是说:在实际开发中,经常需要根据用户是否输入了某个值,来确定是否需要这个条件。 MyBatis中用于动态sql的元素主要有:if、where、trim、set、foreach、choose等
5.1.if+where标签
<select id="listEmp" parameterType="Emp" resultType="Emp">
select * from emp
<where>
<if test="job!=null and job!=''">
and job like concat('%',#{job},'%')
</if>
<if test="deptno!=null and deptno!=''">
and deptno = #{deptno}
</if>
</where>
order by empno
</select>
Emp emp = new Emp();
//emp.setJob("经");
//emp.setDeptno(10); //注意:deptno属性必须为Integer类型
List<Emp> list = mapper.listEmp(emp);
for(Emp e : list) {
System.out.println(e);
}
if+where会实现以下功能:
自动添加where
不需要考虑where后是否加and,mybatis会自动处理
不需要考虑是否加空格,mybatis会自动处理
没有 else 标签,也没有 else if 标签。
注意: job!='' 此处只可以判断是否为空,不能判断是否为某个值。也就是说:job!='经理' 是不好使的。
5.2.choose标签
<select id="listEmp" parameterType="Emp" resultType="Emp">
select * from emp
<where>
<choose>
<when test="job!=null and job!=''">
and job like concat('%',#{job},'%')
</when>
<when test="deptno!=null and deptno!=''">
and deptno = #{deptno}
</when>
<otherwise></otherwise>
</choose>
</where>
order by empno
</select>
Emp emp = new Emp();
emp.setJob("职员");
emp.setDeptno(10);
List<Emp> list = mapper.listEmp(emp);
for(Emp e : list) {
System.out.println(e);
}
choose会实现如下功能:
多个 when 标签中,只能执行一个。也就是说:当一个 when 条件满足并执行后,其它的 when 将不再执行。
当所有 when 都不满足条件时,执行 otherwise 标签。
if 与 choose 的区别:if 相当于java中的if语句; choose相当于java中的switch语句。
5.3.trim标签
trim标签可以在自己包含的内容中加上某些前缀或后缀,与之对应的属性是:prefix、suffix。 还可以把包含内容的开始内容覆盖,即忽略。也可以把结束的某些内容覆盖,对应的属性是:prefixOverrides、suffixOverrides
<insert id="insertEmp1" parameterType="Emp">
insert into emp
<trim prefix="(" suffix=")" suffixOverrides=",">
ename,deptno,
<if test="job!=null and job!=''">
job,
</if>
<if test="hiredate!=null and hiredate!=''">
hiredate,
</if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
#{ename},#{deptno},
<if test="job!=null and job!=''">
#{job},
</if>
<if test="hiredate!=null and hiredate!=''">
#{hiredate},
</if>
</trim>
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
//emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp1(emp);
sqlSession.commit();
System.out.println(result);
注意:
prefix与suffix可以在sql语句中拼接出一对小括号。
suffixOberrides可以将最后一个逗号去掉。
5.4.set标签
set标签主要用于更新操作时使用
<update id="updateEmp" parameterType="Emp">
update emp
<set>
<if test="job!=null and job!=''">
job=#{job},
</if>
<if test="sal!=null and sal!=''">
sal=#{sal},
</if>
</set>
where empno=#{empno}
</update>
Emp emp = new Emp();
emp.setEmpno(7943);
//emp.setJob("职员1");
emp.setSal(3000.0);
int result = mapper.updateEmp(emp);
sqlSession.commit();
System.out.println(result);
注意:
set可以在sql语句中自动加上set关键词。
可以自动将最后的逗号去掉。
上面写法中,必须要保证有一个if成立
5.5.foreach标签
foreach标签可以在sql中迭代一个集合或数组,主要用于拼接in条件。下面是一个批量删除示例:
<delete id="deleteEmp" parameterType="int">
delete from emp where empno in
<foreach collection="array" item="empno" open="(" close=")" separator=",">
#{empno}
</foreach>
</delete>
int[] arr = {7941,7942,7943};
int result = mapper.deleteEmp(arr); //注意:此接口参数应为int数组
sqlSession.commit();
System.out.println(result);
foreach标签的属性:
collection:需要遍历的类型,值有:list、array
item:表示遍历出来的对象
open:表示语句的开始部分
close:表示语句的结束部分
separator:表示每次迭代之间以什么符号为间隔
index:每次迭代的位置索引,就是循环变量
下面使用集合作为参数:
<delete id="deleteEmp" parameterType="int">
delete from emp where empno in
<foreach collection="array" item="empno" open="(" close=")" separator=",">
#{empno}
</foreach>
</delete>
int[] arr = {7941,7942,7943};
int result = mapper.deleteEmp(arr); //注意:此接口参数应为int数组
sqlSession.commit();
System.out.println(result);
6.MyBatis的输入映射和输出映射
6.1.输入映射总结
当sql语句需要一个参数时:
-
接口方法参数为一个基本数据类型;parameterType配置一个基本数据类型;
当sql语句需要多个参数时:
-
接口方法参数为一个实体对象类型;parameterType配置一个实体对象类型;
-
接口方法参数为一个集合类型(List、Map);parameterType配置集合中元素的类型;
注意:当sql语句中需要判断一个基本数据类型的值是否为空时:
值的类型必须为包装类。
即使是只传一个基本数据类型,也要使用实体对象传值。
因为:如果在parameterType中设置Integer类型,那么Mybatis会自动寻找get方法来获取对象属性
值。因此会出现没有get方法异常。
6.2.输出映射总结
当sql语句中的字段名与实体对象中的属性名一致时,使用resultType:
-
返回一条记录时,resultType可以配置成对象类型。
-
返回多条记录时,resultType也要配置成对象类型(表示集合中存储的对象)。
-
返回一条记录,且只有一列时,resultType可以配置成简单数据类型。
当sql语句中的字段名与实体对象中的属性名不一致时,使用resultMap:
-
在resultMap中,显式的书写sql字段名与实体对象属性名的映射
6.3.resultMap的使用
当sql语句中的字段名与实体对象中的字段名不一致时,可以使用resultMap来显式的进行映射。
<!-- 定义一个resultMap,取一个唯一标识id -->
<resultMap type="Emp" id="empResultMap">
<id property="empno" column="empno_0"/>
<result property="ename" column="ename_0"/>
<result property="job" column="job_0"/>
<result property="hiredate" column="hiredate_0"/>
<result property="sal" column="sal_0"/>
<result property="deptno" column="deptno_0"/>
</resultMap>
<!-- 使用resultMap属性,指明使用哪一个resultMap -->
<select id="listEmpAll" resultMap="empResultMap">
select empno empno_0,
ename ename_0,
job job_0,
hiredate hiredate_0,
sal sal_0,
deptno deptno_0
from emp
order by empno_0
</select>
List<Emp> list = mapper.listEmpAll();
for(Emp e : list) {
System.out.println(e);
}
注意:
resultMap标签中的 id 属性:是此resultMap的唯一标识。
resultMap标签中的 type 属性:是此resultMap 映射的实体对象。
resultMap标签中的子标签id,用来配置主键的映射;子标签 result 用来配置其它字段的映射。
子标签id 和子标签 result 中的 property 属性,对应实体类中的属性,column 属性对应sql语句中的字段。
7.MyBatis关联查询
我们知道,在数据库中,表关系有:一对一、多对一、一对多、多对多。 那么在实际开发中,很多查询都是通过多 个表之间的关系,进行多表连接查询的。
那么,在多表连接查询中,MyBatis如何实现输出映射呢,这就要使用MyBatis的关联查询。
MyBatis关联查询有两种:
-
多表连接形式
-
单独查询形式
7.1.多表连接形式
7.1.1.多对一关联查询
7.1.1.1.修改实体类
Dept是one的一方,Emp是many的一方; 在many的一方添加one的一方的对象,这就配置了实体类之间的多对一关联。
7.1.1.2.使用resultMap映射关联
<resultMap type="Emp" id="empResultMap">
<id property="empno" column="empno"/>
<result property="ename" column="ename"/>
<result property="job" column="job"/>
<result property="hiredate" column="hiredate"/>
<result property="sal" column="sal"/>
<result property="deptno" column="deptno"/>
<association property="dept" javaType="Dept">
<id property="deptno" column="ddeptno"/>
<result property="dname" column="ddname"/>
<result property="loc" column="dloc"/>
</association>
</resultMap>
<select id="getEmpById" parameterType="int" resultMap="empResultMap">
select e.*,
d.deptno ddeptno,
d.dname ddname,
d.loc dloc
from emp e left join dept d
on e.deptno=d.deptno
where e.empno = #{empno}
</select>
<select id="listEmpAll" resultMap="empResultMap">
select e.*,
d.deptno ddeptno,
d.dname ddname,
d.loc dloc
from emp e left join dept d
on e.deptno=d.deptno
</select>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpById(7369);
System.out.println(emp);
System.out.println(emp.getDept());
List<Emp> list = mapper.listEmpAll();
for(Emp e : list) {
System.out.println(e);
System.out.println(e.getDept());
}
association表示配置单个对象的关联映射 :
association标签中的property:many的一方的实体类中,添加的属性名。
association标签中的javaType:many的一方的实体类中,添加的属性类型。
id标签和result标签中的property:one的一方的实体类中的属性名。
id标签和result标签中的column:one的一方的,查询出来的字段名。
7.1.2.一对多关联查询
7.1.2.1.修改实体类
Dept是one的一方,Emp是many的一方; 在one的一方添加many的一方的集合,这就配置了实体类之间的一对多关联。
7.1.2.2.使用resultMap映射关联
<resultMap type="Dept" id="deptResultMap">
<id property="deptno" column="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="empList" ofType="Emp">
<id property="empno" column="eempno"/>
<result property="ename" column="eename"/>
<result property="job" column="ejob"/>
<result property="hiredate" column="ehiredate"/>
<result property="sal" column="esal"/>
<result property="deptno" column="edeptno"/>
</collection>
</resultMap>
<select id="getDeptById" parameterType="int" resultMap="deptResultMap">
select d.*,
e.empno eempno,
e.ename eename,
e.job ejob,
e.hiredate ehiredate,
e.sal esal,
e.deptno edeptno
from dept d left join emp e
on d.deptno=e.deptno
where d.deptno = #{deptno}
</select>
<select id="listDeptAll" resultMap="deptResultMap">
select d.*,
e.empno eempno,
e.ename eename,
e.job ejob,
e.hiredate ehiredate,
e.sal esal,
e.deptno edeptno
from dept d left join emp e
on d.deptno=e.deptno
</select>
Dept dept = mapper.getDeptById(10);
System.out.println(dept);
List<Emp> empList = dept.getEmpList();
for(Emp e : empList) {
System.out.println(e);
}
List<Dept> deptList = mapper.listDeptAll();
for(Dept d : deptList) {
System.out.println(d);
List<Emp> el = d.getEmpList();
for(Emp e : el) {
System.out.println(e);
}
}
collection表示配置多个对象的集合的关联映射 :
collection标签中的property:one的一方的实体类中,添加的集合属性名。
collection标签中的ofType:one的一方的实体类中,添加的集合中的元素类型。
id标签和result标签中的property:many的一方的实体类中的属性名。
id标签和result标签中的column: many的一方的,查询出来的字段名。
7.2.单独查询形式
7.2.1.多对一关联查询
7.2.1.1.先在one的一方添加关联查询
<select id="getDeptByIdLazy" parameterType="int" resultType="Dept">
select * from dept where deptno = #{deptno}
</select>
注意:此查询可以不在接口中书写响应的方法
7.2.1.2.many的一方添加查询
<resultMap type="Emp" id="empResultMap">
<id property="empno" column="empno"/>
<result property="ename" column="ename"/>
<result property="job" column="job"/>
<result property="hiredate" column="hiredate"/>
<result property="sal" column="sal"/>
<result property="deptno" column="deptno"/>
<association property="dept" javaType="Dept"
select="com.neusoft.mapper.DeptMapper.getDeptByIdLazy" column="deptno" />
</resultMap>
<select id="getEmpById" parameterType="int" resultMap="empResultMap">
select * from emp where empno = #{empno}
</select>
7.2.1.3.测试
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpById(7369);
//System.out.println(emp);
//System.out.println(emp.getDept());
测试时,可以在日志中看到,执行了两次sql语句。
7.2.2.一对多关联查询
7.2.2.1.先在many的一方添加关联查询
<select id="getEmpByIdLazy" parameterType="int" resultType="Emp">
select * from emp where deptno = #{deptno}
</select>
注意:此查询可以不在接口中书写响应的方法
7.2.2.2.one的一方添加查询
<resultMap type="Dept" id="deptResultMap">
<id property="deptno" column="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="empList" ofType="Emp"
select="com.neusoft.mapper.EmpMapper.getEmpByIdLazy" column="deptno"/>
</resultMap>
<select id="getDeptById" parameterType="int" resultMap="deptResultMap">
select * from dept where deptno = #{deptno}
</select>
7.2.2.3.测试
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptById(10);
System.out.println(dept);
List<Emp> list = dept.getEmpList();
for(Emp emp : list) {
System.out.println(emp);
}
测试时,可以在日志中看到,执行了两次sql语句。
7.3.关联映射总结
使用resultMap实现关联映射时:
-
使用association标签完成多对一或一对一映射。
-
association标签:将关联查询信息映射到一个po对象中。
-
association标签中的javaType属性:表示该po对象的类型。
-
association标签中的select属性:表示应用哪一个关联查询。
-
association标签中的column属性:表示应用关联查询的条件。
-
-
使用collection标签完成一对多,多对多映射。
-
collection标签:将关联查询信息映射到一个list集合中。
-
collection标签的ofType属性:表示该集合中的元素对象的类型。
-
collection标签中的select属性:表示应用哪一个关联查询。
-
collection标签中的column属性:表示应用关联查询的条件。
-
7.4.关联查询的延迟加载
延迟加载:执行查询时,关联查询不会立即加载。只有在使用关联数据时才会加载。 优点:按需加载,提高效率。
注意: 要使用关联查询的延迟加载,就必须要使用单独查询形式。并且,需要先启用MyBatis的延迟加载配置(需要配置两项):
lazyLoadingEnabled:延迟加载的全局开关(默认为false)。
aggressiveLazyLoading:延迟加载整个对象(默认为true; false:对象的每个属性都会延迟加载,即属性按需加载
<!-- 如果aggressiveLazyLoading为true,那么lazyLoadingEnabled即使为true也无效。 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
重新测试上面的关联查询(要测试单独查询形式): 当不需要使用关联数据时,关联查询将不会执行。
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptById(10);
8.MyBatis的缓存机制
MyBatis是一个持久层框架,经常访问物理数据库。为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。 MyBatis使用了缓存机制。
缓存内的数据是对物理数据源中的数据的复制,而缓存在内存中,应用程序在运行时从缓存中读写数据,就可以大大提高读写速度。
MyBatis缓存分为一级缓存和二级缓存:
一级缓存是SqlSession级别的缓存。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存。
二级缓存是mapper级别的缓存,同一个命名空间下的多个SqlSession可以共用一个二级缓存,二级缓存是跨SqlSession的。有几个namespace,就有几个二级缓存。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同,即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
在MyBatis中,一二级缓存都是由HashMap实现的。
8.1.一级缓存
SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession();
FirstTitle ft1 = sqlSession.selectOne("ft.selectFirstTitleById",1);
//如果执行了commit,那么第二次将重新查询。否则,直接从缓存中取数据
//sqlSession.commit();
FirstTitle ft2 = sqlSession.selectOne("ft.selectFirstTitleById",1);
System.out.println(ft1==ft2);
sqlSession.close();
Mybatis默认开启一级缓存
如果sqlSession执行了commit操作(执行插入、更新、删除),将会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
8.2.二级缓存
8.2.1.二级缓存的实现
1.Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存的总开关。
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.在mapper下开启二级缓存。如果使用注解,那么在接口上书写@CacheNamespace
<mapper namespace="ft">
<cache/>
… …
</mapper>
3.实体类要实现序列化
public class FirstTitle implements Serializable{}
为什么要实现序列化,因为二级缓存数据有可能是存储在硬盘上或其他形式。
4.测试
SqlSession sqlSession1 = …
SqlSession sqlSession2 = …
FirstTitle ft1 = sqlSession1.selectOne("ft.selectFirstTitleById",1);
//关闭session,才会向二级缓存中写入
sqlSession1.close();
FirstTitle ft2 = sqlSession2.selectOne("ft.selectFirstTitleById",1);
5.通过日志查看二级缓存
因为二级缓存数据有可能是存储在硬盘上的,所以当ft2对象从二级缓存中读取出来后,与ft1对象肯定不是同一个对象了。因此也就不能使用==来判断缓存是否有效。此时可以通过查看日志来了解二级缓存机制。
SqlSession sqlSession1 = …
SqlSession sqlSession2 = …
SqlSession sqlSession3 = …
FirstTitle ft1 = sqlSession1.selectOne("ft.selectFirstTitleById",1);
sqlSession1.close();
FirstTitle ft2 = sqlSession2.selectOne("ft.selectFirstTitleById",1);
ft2.setTitleName("游戏");
sqlSession2.update("ft.updateFirstTitle",ft2);
//MyBatis监测到数据改变后会清空二级缓存,以避免脏读。
sqlSession2.commit();
sqlSession2.close();
FirstTitle ft3 = sqlSession3.selectOne("ft.selectFirstTitleById",1);
sqlSession3.close();
8.2.2.二级缓存的应用及局限
二级应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用二级缓存降低数据库访问量,提高访问速度(比如:耗时较高的统计分析sql、电话账单查询sql等)。
二级缓存局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好。比如有一些海量数据,对它的查询访问量大,但是要求每次必须查询最新数据。此时如果使用二级缓存,就会出现:因为二级缓存区域以mapper为单位划分,当微量数据被修改后,其它所有缓存数据都会全部清空。
9.使用注解实现MyBatis映射
MyBatis也支持使用注解来配置映射语句。 主要有四种注解来实现增删改查:@Select、@Insert、@Update、@Delete
在映射文件中使用的所有的CRUD操作,都可以使用注解的形式完成。而且,当使用基于注解的映射器接口时,就不再需要映射配置文件了。
在实际开发中,可以单独使用映射文件,也可以单独使用注解,也可以混合使用。
9.1.使用注解实现CRUD
package com.neusoft.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import com.neusoft.po.Emp;
public interface EmpMapper {
@Select("select * from emp where empno = #{empno}")
public Emp getEmpById(int empno);
//这里必须使用泛型
@Select("select * from emp order by empno")
public List<Emp> listEmpAll();
}
9.2.使用注解实现关联查询
//一对多:
@Select("select * from dept where id = #{id}")
@Results({
@Result(id=true,property="id",column="id"),
@Result(property="name",column="name"),
@Result(property="loc",column="loc"),
@Result(property="实体类里面的属性",column="id",javaType=List.class,
many=@Many(select="多方的接口.根据一方的编号查询多方的集合"))
})
public Dept selectDeptById(Integer id);
//多对一:
@Select("select * from emp where eid = #{eid}")
@Results({
@Result(id=true,property="eid",column="eid"),
@Result(property="ename",column="ename"),
@Result(property="eage",column="eage"),
@Result(property="实体中的对象",column="外键列",javaType=一方类.class,
one=@One(select="一方接口.根据一方编号查询信息"))
})
public Emp selectEmpById(Integer eid);