Mapper开发模式详解

我们在前面简单的写了一个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创建的对象进行引用里面的方法进行使用了。
        */
    }

    .....
}

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值