我们在前面简单的写了一个mapper模式的例子,但是有很多的瑕疵,也没有过多的说明,因为在开发中mapper模式越来越流行,也确实有自己的独到的优点,所以就配合前面讲的所有小的知识点,再去进行一次完善点的mapper的小项目的讲解。
Mapper开发模式详解
为什么要用mapper模式?
一个模块的流行肯定有它流行的好处,相对于dao模式来说,mapper省略了更多的代码,也更贴切Mybatis框架的特性,而且弥补了dao模式下一些缺陷。为什么这样说呢?
首先来看一下dao模式的缺陷:
缺陷1:字符串参数多,容易出错,且编译阶段不会报错。
sqlSession.insert("com.yht.mybatis.dao.EmpMapper.insertEmp",emp);
在上面的一段代码中,我们需要利用mapper配置文件中的namespace名.查询语句的id来获取这个方法,然后后面再传入参数,这样做不仅会很可能写错,而且都是很长的字符串,加上编译阶段容易不会报错,所以必须等到运行阶段才会发现,且不易修改。
缺陷2:传入参数无法保证类型正确
还是上面的一段代码,我们这里进行传入的是一个emp对象,但是其实这里不管是什么对象,在编译阶段都不会报错,必须要等到运行阶段才会报错。
因为有这上面的两个缺陷,所以现在更流行mapper开发模式,后面会讲解,mapper是如何弥补上面两种错误的。
mapper的准备
文件框架
数据库的表(表名为emp)
pom.xml
从大到小:我们先从主配置文件开始进行配置
依赖:数据库连接依赖 、 Mybatis依赖 、 日志工具依赖 、测试工具依赖 、 Lombok依赖
还需要一个插件: maven-resources-plugin插件
我们在之前编写项目的时候,是mapper的配置文件放在resource里面,然后在外面建一个和dao层名称一模一样的包名,来让他们编译后能够在一个文件夹下,但是这样会比较麻烦,项目也会变得不规整,我们这里是进行mapper模式开发,所以就不需要dao层了,用来代替原来dao层的是mapper层,我们会创建一个mapper接口,那么这个接口命名会和这个配置文件名称一致,所以我们把他们共同放在mapper层是最好不过了,但是maven不会从java下进行读取配置文件,我们的解决办法是借用一个插件来进行辅助。
我们也需要在pom.xml文件中进行添加。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yht</groupId>
<artifactId>myMapper</artifactId>
<version>1.0.0</version>
<!-- 依赖库 -->
<dependencies>
<!-- 数据库连接依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<!--日志工具依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
<!--Mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- 测试工具依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- 插件:是可以在任何地方编译配置文件 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
</plugins>
<!-- 为上面的插件提供数据源 -->
<resources>
<!-- 第一个范围,就是我们的resources下,正常的路径 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<!-- 任何路径,任何名字的.xmlw文件和properties文件 -->
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<!-- 第二个范围,让之可以在java文件夹下进行配置文件的编译 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/dbmaven?serverTimezone=UTC&characterEncoding=UTF-8
username=root
password=
log4j.properties
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.com.yht.mybatis.mapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
mybatis-config.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>
<properties resource="db.properties" />
<!-- 为实体类都进行起一个别名,这里作用是只要在下面路径里的类,默认用类名做别名,后面只要需要写
实体类的全限定名时,都可以直接用类名代替-->
<typeAliases>
<package name="com.yht.mybatis.bean"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!-- 连接mapper配置文件路径 -->
<mappers>
<mapper resource="com/yht/mybatis/mapper/EmpMapper.xml"/>
</mappers>
</configuration>
Empmapper.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="com.yht.mybatis.mapper.EmpMapper">
<!-- 这里进行了一个结果映射的别名转化 -->
<resultMap id="useResultMap" type="Emp">
<id column="e_id" property="id"/>
<result column="e_name" property="name"/>
<result column="e_age" property="age"/>
<result column="e_birthday" property="birthday"/>
<result column="e_salary" property="salary"/>
</resultMap>
<!-- 增加 -->
<insert id="insertEmp" useGeneratedKeys="true" keyColumn="e_id" keyProperty="id">
insert into emp (e_name,e_age,e_birthday,e_salary) values (#{name},#{age},#{birthday},#{salary})
</insert>
<!--删除-->
<delete id="deleteEmp">
delete from emp where e_id = #{id}
</delete>
<!--修改-->
<update id="updateEmp">
update emp set e_name = #{name} , e_age = #{age} where e_id = #{id}
</update>
<!--按编号查询-->
<select id="findEmpById" resultMap="useResultMap">
select e_id,e_name,e_age,e_birthday,e_salary from emp where e_id = #{id}
</select>
<!--查询所有-->
<select id="findAllEmp" resultMap="useResultMap">
select e_id,e_name,e_age,e_birthday,e_salary from emp
</select>
</mapper>
Mapper模式开发
实体类Emp.java
package com.yht.mybatis.bean;
import lombok.*;
import java.math.BigDecimal;
import java.util.Date;
@Getter@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Emp {
private Integer id;
private String name;
private Integer age;
private Date birthday;
private BigDecimal salary;
}
工具类MybatisUtil.java
package com.yht.mybatis.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 MybatisUtil {
private static SqlSessionFactory factory = null;
static {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
factory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static SqlSession getSqlSession(){
return factory.openSession();
}
}
Mapper接口EmpMapper.java
这是这个模式的核心接口,里面只需要写上之前dao模式下的方法,不过这里需要遵循四个条件:
- sql映射文件的namespace必须和mapper接口的全限定类名保持一致
- mapper接口的接口方法名必须和xml中的sql语句id保持一致
- mapper接口的接口方法形参类型必须和sql语句的输入参数类型保持一致
- mapper接口的接口方法返回类型必须和sql语句的resultType保持一致
其中前两个条件是必须需要满足的,否则会报错,后面两个条件最好满足,如果真的没有写在一些条件下是不会报错的。
package com.yht.mybatis.mapper;
import com.yht.mybatis.bean.Emp;
import java.util.List;
public interface EmpMapper {
void insertEmp(Emp emp);
void deleteEmp(Integer id);
void updateEmp(Emp emp);
Emp findEmpById(Integer id);
List<Emp> findAllEmp();
}
Service层接口EmpService.java
完善项目的结构,所以也创建了service层,功能就是对mapper层和用户之间添加一层保护和便捷的服务,把重要的语句封装起来,让用户只需要传值和取值就行了。所以这里的方法是和mapper的接口一样的:
package com.yht.mybatis.mapper;
import com.yht.mybatis.bean.Emp;
import java.util.List;
public interface EmpMapper {
void insertEmp(Emp emp);
void deleteEmp(Integer id);
void updateEmp(Emp emp);
Emp findEmpById(Integer id);
List<Emp> findAllEmp();
}
Service层实现类EmpServiceImpl.java
这里对上面的方法进行一个实现,达到用户只用传值和取值就行:
这里要对两行中重要代码进行简单解读:
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
mapper.insertEmp(emp);
这里是利用sqlSession对象调用getMapper方法,然后传入EmpMapper接口的字节码对象,再返回一个EmpMapper的对象,通过这个对象来调用了里面的方法,中间经过封装,创建了一个实现类,然后其实调用的方法是中间隐藏的实现类的实现的方法。
package com.yht.mybatis.service.impl;
import com.yht.mybatis.bean.Emp;
import com.yht.mybatis.mapper.EmpMapper;
import com.yht.mybatis.service.EmpService;
import com.yht.mybatis.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class EmpServiceImpl implements EmpService {
@Override
public void insertEmp(Emp emp) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
mapper.insertEmp(emp);
sqlSession.commit();
sqlSession.close();
}
@Override
public void deleteEmp(Integer id) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
mapper.deleteEmp(id);
sqlSession.commit();
sqlSession.close();
}
@Override
public void updateEmp(Emp emp) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
mapper.updateEmp(emp);
sqlSession.commit();
sqlSession.close();
}
@Override
public Emp findEmpById(Integer id) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.findEmpById(id);
sqlSession.commit();
sqlSession.close();
return emp;
}
@Override
public List<Emp> findAllEmp() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> list = mapper.findAllEmp();
sqlSession.commit();
sqlSession.close();
return list;
}
}
测试类EmpTest.java
package com.yht.mybatis.test;
import com.yht.mybatis.bean.Emp;
import com.yht.mybatis.service.EmpService;
import com.yht.mybatis.service.impl.EmpServiceImpl;
import com.yht.mybatis.utils.MybatisUtil;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
public class MapperTest {
@Test
public void testGetSession(){
MybatisUtil.getSqlSession();
}
@Test
public void testInsertEmp(){
Emp emp = new Emp(null,"鸣人",12,new Date(),new BigDecimal(100));
EmpService service = new EmpServiceImpl();
service.insertEmp(emp);
System.out.println(emp);
}
@Test
public void testDeleteEmp(){
EmpService service = new EmpServiceImpl();
service.deleteEmp(1);
}
@Test
public void testUpdateEmp(){
Emp emp = new Emp(2,"佐助",13,new Date(),new BigDecimal(1000));
EmpService service = new EmpServiceImpl();
service.updateEmp(emp);
}
@Test
public void testFindEmpById(){
EmpService service = new EmpServiceImpl();
Emp emp = service.findEmpById(3);
System.out.println(emp);
}
@Test
public void testFindAllEmp(){
EmpService service = new EmpServiceImpl();
List<Emp> list = service.findAllEmp();
for(Emp emp : list){
System.out.println(emp);
}
}
}
到这里就完成了,如果读者想要自己实现这个小项目,需要把里面的一些东西换成自己的,比如自己电脑的数据库配置,自己进行表的创建。
这个小项目里面融合了前面介绍的别名、日志、结果映射、获取自动生成主键,还添加了一个可以编译配置文件的插件,以及service层
Mapper原理解读
mapper模式的高明之处就是省略了dao层的实现类,然后封装起来了一个实现类,让我们不用再去编写实现类和考虑实现类中的错误了
最重要的还是这两句代码:
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
mapper.insertEmp(emp);
我们看前面的mapper是一个接口,它是一个引用,利用多态来调用了一个实现类的方法,那么它的实现类是谁呢?
我们来修改代码:
在service层的实现类中的一个重载方法中添加一句代码:
@Override
public Emp findEmpById(Integer id) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.findEmpById(id);
System.out.println(mapper.getClass());
sqlSession.commit();
sqlSession.close();
return emp;
}
我们来打印一下这个引用指向的真实的类:
我们发现,指向的是一个叫$Proxy5的类中,proxy:代理、代理人. 顾名思义就是 这个类在代理了这个实现类,发挥其作用。
这里其实是用了动态代理技术,具体的会在spring框架中更好的介绍。
那么具体是如何来进行创建的呢?
public class $Proxy5 implements EmpMapper {
@Override
public void insertEmp(Emp emp) {
/*
1.通过反射来获取实现类的全限定名(传入了一个class字节码对象)
因为四个要求里第一个要求必须让类名和mapper配置文件中的namespace名一致
所以就相当于是获取了mapper配置文件
2.然后因为是实现了mapper接口,所以就获取了方法名,然后再按照方法名去获取
mapper配置文件中响应的SQL语句。
3.然后全限定名也有了,SQL语句的id值也有了,就可以:
session.insert(namesapce+id,参数);
然后就相当于实现了mapper接口里面的方法。
就可以用EmpMapper创建的对象进行引用里面的方法进行使用了。
*/
}
.....
}