Java框架-mybatis-基础

1. 概念

1.1 概念引入

  • 框架( Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法。或者说是可被应用开发者定制的应用骨架。
  • 框架的优点:
    1. 框架已经实现了一些功能,使用时不需重复实现这些功能,提高开发效率;
    2. 使用框架后,我们软件的架构更加稳定、开发流程必须按照框架约定进行,而优秀的框架是所有开发人员都熟悉的,任何人进入项目团队后可以更快适应开发,同时框架良好的扩展性更易于后期维护,减少项目成本。

1.2 javaweb常用框架

表现层:SpringMVC、Struts2

业务层:Spring可对service层提供统一的事务控制。

数据访问层:JDBC、自定义框架封装jdbc、JDBCTemplate(Spring)、apache DbUtils、Hibernate框架、Jpa、Spring Data Jpa、Mybatis等

1.3 传统JDBC开发弊端

  1. 频繁创建连接对象和释放,容易浪费系统资源影响性能;

  2. sql语句的定义、参数设置、结果集处理存在硬编码,不好维护和修改

    . (3) 结果集处理存在重复代码,每次都要遍历ResultSet,获取一行数据,封装为对象处理麻烦。

2. MyBatis框架

  1. mybatis早期版本叫做ibatis,目前代码托管在github。

  2. mybatis是对jdbc的封装,是一个持久层的框架。开发者只需要关注业务本身,不需要花费精力去处理其他过程代码

  3. mybatis是通过xml或者注解进行配置,实现java对象与sql语句的对应关系(映射)。

3. MyBatis框架-构建项目

3.1 准备环境

1.准备数据库

-- 1.创建数据库
CREATE DATABASE mybatis DEFAULT CHARACTER SET utf8;

-- 2.创建用户表
DROP TABLE IF EXISTS USER;

CREATE TABLE USER (
  id INT(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(32) NOT NULL COMMENT '用户名称',
  birthday DATETIME DEFAULT NULL COMMENT '生日',
  sex CHAR(1) DEFAULT NULL COMMENT '性别',
  address VARCHAR(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY  (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


INSERT  INTO USER(id,username,birthday,sex,address) VALUES 
(41,'旺旺','2018-08-27 17:47:08','男','北京'),(42,'小二王','2018-09-02 15:09:37','女','南京'),
(43,'明明','2018-01-04 11:34:34','女','东京'),
(45,'狗蛋','2017-02-04 12:04:06','男','西安'),
(46,'隔壁老王','2015-05-07 17:37:26','男','济南'),
(48,'茱莉','2017-07-07 11:44:00','女','华盛顿');

SELECT * FROM USER;

2.创建项目

3.创建实体类

package com.azure.entity;

import java.util.Date;

public class User {
    private int id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public User(int id, String username, Date birthday, String sex, String address) {
        this.id = id;
        this.username = username;
        this.birthday = birthday;
        this.sex = sex;
        this.address = address;
    }

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getUsername() {return username;}

    public void setUsername(String username) {this.username = username;}

    public Date getBirthday() {return birthday;}

    public void setBirthday(Date birthday) {this.birthday = birthday;}

    public String getSex() {return sex;}

    public void setSex(String sex) {this.sex = sex;}

    public String getAddress() {return address;}

    public void setAddress(String address) {this.address = address;}
}

4.添加依赖

<?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.azure</groupId>  
  <artifactId>day47projects_mybatis01</artifactId>  
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <!--mybatis支持包-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>
    <!--数据库驱动包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.30</version>
    </dependency>
    <!--日志包-->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
  </dependencies>

</project>

3.2 配置SqlMapConfig.xml

  • 该文件要放在src/main/resources目录下
<?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>
    <!--default 表示默认使用哪一个运行环境配置-->
    <environments default="mysql">
        <!--id="mysql" 表示mysql的运行环境配置-->
        <environment id="mysql">
            <!--事务管理器配置:基于JDBC的事务控制-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据库连接池方式,type值有三个UNPOOLED,POOLED,JNDI
                    unpooled,不使用连接池
                    pooled,使用mybatis内置数据库连接池(推荐使用,与mybatis配合使用性能最好)
                    JNDI,使用javaee容器(服务器)内置的数据库连接池
            -->
            <dataSource type="pooled">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql:///mybatis?characterEncoding=utf8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
    </environments>

    <!--加载接口的映射文件-->
    <mappers>
        <!--必须放到类路径下-->
        <mapper resource="com/azure/dao/IUserDao.xml"></mapper>
        <!--注意:如果这里是/,那么在resources创建文件夹时也要对应的写成"com/azure/dao",而不能用"."分隔!!-->
    </mappers>
</configuration>

3.3 dao层

3.3.1 面向接口编程,创建接口

/*
数据访问层接口
 */
public interface IUserDao {

    List<User> findAll();
}

3.3.2 dao接口映射

  • 接口映射文件相当于dao接口的实现类!

  • 注意:映射文件存放目录要与SqlMapConfig.xml加载映射的路径要一致。如果SqlMapConfig.xml是用/分隔路径,那么再创建映射文件的存放目录时也一定要以/分隔,否则会报错无法找到映射文件!!原因详见“3.6节 注意事项”

<?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命名空间,用于定义实现哪个接口,需要写所映射接口的类全名-->
<mapper namespace="com.azure.dao.IUserDao">
    <!--
    select:代表实现查询操作
    id:用于设置实现接口中哪个方法
    resultType:用于设置接口方法返回的泛型类型
    标签体:执行的sql语句
    -->
    <select id="findAll" resultType="com.azure.entity.User">
        select * from user
    </select>
</mapper>

3.4 引入日志文件

  • 日志文件 log4j.properties要放在src/main/resources目录下
  • 注意:
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

3.5 dao层测试类

public class UserDaoTest {
    public static void main(String[] args) throws Exception {
        //1.获取文件流
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建工厂构造器
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.根据文件和构造器创建工厂
        SqlSessionFactory factory = builder.build(is);
        //4.使用工厂创建SqlSession对象
        SqlSession sqlSession = factory.openSession();
        //5.创建接口代理对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        //查看是否代理对象
        System.out.println(userDao.getClass());
        //6.调用方法
        List<User> users = userDao.findAll();
        System.out.println(users);
        //7.关闭并释放资源
        sqlSession.close();
        is.close();
    }
}

3.6 注意事项

  1. 需要主配置(SqlMapConfig.xml)加载接口映射文件路径;

  2. 映射文件的namespace对应接口路径(相当于实现类);

  3. 接口的方法与接口对应映射的方法名称要一一对应:

    在这里插入图片描述

  4. 在IDEA内resources目录下创建文件夹与java目录下创建文件夹不同。java目录下创建多级目录可以用".“分隔,比如com.azure.dao是三级目录。而在resources目录下创建多级目录只能用”/“分隔,如果仍以”."分隔,比如com.azure.dao,实际上只是创建一个文件夹,文件夹名就是com.azure.dao。

4. 自定义框架(了解)

系统架构分析:

最后是使用dao的代理对象查询数据库,首先得生成代理对象,分析如下:
  1. 先对指定的dao接口生成代理对象,通过sqlSession对象调用方法获得:<T> T getMapper(Class<T> daoClass);
  2. 获得sqlSession对象,对SqlSession接口进行实现
  3. 而SqlSession是通过SqlSessionFactory工厂创建的,故要先定义工厂

4.1 SqlSession接口、DefaultSqlSession接口默认实现类

4.1.1 SqlSession接口

/*
SqlSession接口定义
 */
public interface SqlSession {
    /**
     * 根据指定的dao接口获得代理对象
     * @param daoClass
     * @param <T>
     * @return dao代理对象
     */
    <T> T getMapper(Class<T> daoClass);
}

4.1.2 DefaultSqlSession接口默认实现类

public class DefaultSqlSession implements SqlSession {
    /**
     * 根据指定的dao接口获得代理对象
     *
     * 使用jdk动态代理proxy
     * @param daoClass
     * @return dao代理对象
     */
    public <T> T getMapper(Class<T> daoClass) {
        /*
        代理实现方式:
        静态代理
        jdk动态代理
            static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
                1.ClassLoader loader:类加载器
                2.Class<?>[] interfaces:需要代理的接口类对象数组(代理IUserDao获取动态代理虚拟实现对象)
                3.InvocationHandler h:事件处理程序,当执行代理对象方法时候会触发事件处理程序,把当前方法传入事件处理程序中。
        cglib代理(spring)
         */
        return (T) Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),new Class[]{daoClass},new MapperProxy());
    }
}

4.1.3 SqlSessionFactory工厂

public class SqlSessionFactory {
    /*
    创建SqlSession
     */
    public static SqlSession openSession(){
        return new DefaultSqlSession();
    }
}

4.1.4 MapperProxy代理 (事件处理程序)

public class MapperProxy implements InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1.获取当前执行的方法的名称
        String methodName = method.getName();
        //2.获得方法所属类全名
        String className = method.getDeclaringClass().getName();
        //3.获取映射配置文件数据,由于映射文件数据内容较多,而且映射文件数目也多
        // 可以使用一个类(Mapper)将每个映射文件数据封装起来,然后再用一个Map将所有映射文件的Mapper对象封装
        Map<String, Mapper> mappers = Configuration.getInstance().getMappers();
        //获取当前方法的映射配置
        Mapper mapper = mappers.get(className + "." + methodName);
        //4.执行sql命令并返回数据
        //判断sql语句类型
        if (mapper.getSqlType().equalsIgnoreCase("select")) {
            //使用工具类执行sql
            return Executor.selectList(mapper);
        }
            return null;
    }
}

4.1.5 Executor工具类

/*
执行sql语句工具类
 */
public class Executor {

    //获取数据库连接池
    private static DataSource ds = Configuration.getDataSource();

    public static Object selectList(Mapper mapper) {
        //获取sql
        String sql = mapper.getSql();
        //获取返回值类型
        String resultType = mapper.getResultType();
        /*
        目标:执行sql语句,封装数据到List<>返回
         */
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        //返回集合数据,使用泛型
        List list = new ArrayList<>();

        try {
            conn = ds.getConnection();
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();

            //获取结果集元数据
            ResultSetMetaData metaData = rs.getMetaData();
            //通过元数据获取列数
            int columnCount = metaData.getColumnCount();
            //获取返回类型的字节码类型对象(用于后面获取改类的对象实例封装数据)
            Class resultTypeClazz = Class.forName(resultType);

            //迭代结果集
            while (rs.next()) {
                //实例返回对象
                Object obj = resultTypeClazz.getConstructor().newInstance();
                //循环获取每条数据封装
                for (int i = 1; i <= columnCount; i++) {
                    //根据列索引获取列名
                    String columnName = metaData.getColumnName(i);
                    //根据列索引获取每个字段的值
                    Object value = rs.getObject(i);
                    //使用反射根据属性名和字节码对象获得属性描述器对象
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClazz);
                    //根据属性描述器获取set封装方法
                    Method setMethod = propertyDescriptor.getWriteMethod();

                    //将列名和值封装对对象中,利用对象set方法对象的invoke执行封装
                    setMethod.invoke(obj,value);
                }
                //添加数据到集合中
                list.add(obj);
            }

            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
                ps.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
return null;
    }
}

4.1.6 Configuration解析并封装配置文件数据

/*
使用Configuration解析并封装配置文件数据
 */
public class Configuration {
    //使用单例模式创建对象并解析数据,确保当前对象被实例化一次
    //1. 创建静态类对象
    private static Configuration configuration = null;

    //2.定义私有构造函数
    private Configuration() {
        //在实例化的时候解析配置文件数据并封装
        //加载主配置文件数据SqlMapConfig.xml
        loadSqlMapConfig();
    }

    //3.提供共有静态方法返回对象
    public static Configuration getInstance() {
        //只有调用方法才会创建对象,后面调用所获取的都是同一个对象(即第一次创建的对象)--单例模式的选择原因
        if (configuration == null) {
            configuration = new Configuration();
        }
        return configuration;
    }

    /**
     * 用于加载主配置文件
     * 使用jsoup对xml文件进行解析
     */
    private void loadSqlMapConfig() {
        try {
            //1.根据文件路径获取xml的Document对象
            String path = Resources.class.getResource("/SqlMapConfig.xml").getPath();//使用类字节码对象获取文件路径
            Document document = Jsoup.parse(new File(path), "utf-8");
            /*获取Document对象的数据并封装数据库连接的4个数据到configuration对象中
                原因:所有的配置文件数据都封装到configuration对象中,数据库连接可以通过configuration对象获取配置文件中所需的这4个数据
            */
            this.driver = document.selectFirst("property[name='driver']").attr("value");
            this.url = document.selectFirst("property[name='url']").attr("value");
            this.username = document.selectFirst("property[name='username']").attr("value");
            this.password = document.selectFirst("property[name='password']").attr("value");

            //2.加载接口映射配置文件
            //2.1 获取mapper标签列表,循环遍历加载
            Elements mapperElements = document.select("mapper");
            for (Element mapperElement : mapperElements) {
                String mapperPath = mapperElement.attr("resource");
                //加载每个映射接口配置文件
                loadMapperData(mapperPath);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载当前接口配置文件的所有数据到mappers中
     * @param mapperPath
     */
    private void loadMapperData(String mapperPath) throws IOException {
        //根据文件路径获取Document对象
        String path = Resources.class.getResource("/" + mapperPath).getPath();//注意:这里必须拼接/
        Document document = Jsoup.parse(new File(path),"utf-8");
        //解析数据
        Element mapperElement = document.selectFirst("mapper");
        String namespace = mapperElement.attr("namespace");
        Elements childrenElements = mapperElement.children();
        //遍历子标签封装数据
        for (Element childrenElement : childrenElements) {
            String id = childrenElement.attr("id");
            String resultType = childrenElement.attr("resultType");
            String sql = childrenElement.text();
            String sqlType = childrenElement.nodeName();
            //将数据封装到Mapper里面
            Mapper mapper = new Mapper();
            mapper.setNamespace(namespace);
            mapper.setId(id);
            mapper.setResultType(resultType);
            mapper.setSql(sql);
            mapper.setSqlType(sqlType);
            //将Mapper封装mappers(Map<String,Mapper>)里面
            //Map的键=接口类全名.方法名
            //map的值=映射文件的方法实现标签,也就是Mapper类
            mappers.put(namespace + "." + id, mapper);
        }
    }

    /**
     * 解析配置文件并封装到当前类中
     */
    //1.定义configuration对象的成员变量,用于封装数据库连接字符串信息
    private String driver;
    private String url;
    private String username;
    private String password;

    //2.定义成员方法
    //2.1 提供公共静态的方法返回数据库连接池对象
    public static DataSource getDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        //获取Configuration实例对象,此时Configuration对象第一次实例
        Configuration configuration = getInstance();
        //设置数据库信息
        druidDataSource.setDriverClassName(configuration.driver);
        druidDataSource.setUrl(configuration.url);
        druidDataSource.setUsername(configuration.username);
        druidDataSource.setPassword(configuration.password);
        return druidDataSource;
    }

    //2.2 由于主配置文件中可以关联多个接口映射文件数据,需要用集合封装所有接口映射文件
    //Map一个键值对可以封装一个映射文件数据,所以使用Map集合
    //Map的键=接口类全名.方法名
    //map的值=映射文件的方法实现标签,也就是Mapper类
    private Map<String,Mapper> mappers = new HashMap<>();
    public Map<String,Mapper> getMappers(){
        return mappers;
    }

    //getter&setter
    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

4.1.7 dao层接口和dao配置文件

接口

/*
数据访问层接口
 */
public interface IUserDao {

    List<User> findAll();
}

配置文件

<?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命名空间,用于定义实现哪个接口,需要写所映射接口的类全名-->
<mapper namespace="com.azure.dao.IUserDao">
    <!--
    select:代表实现查询操作
    id:用于设置实现接口中哪个方法
    resultType:用于设置接口方法返回的泛型类型
    标签体:执行的sql语句
    -->
    <select id="findAll" parameterType="int" resultType="com.azure.entity.User">
        select * from user
    </select>
</mapper>

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/83449908
今日推荐