美文网首页
【Java】07_JavaWeb_Framework(开源框架)

【Java】07_JavaWeb_Framework(开源框架)

作者: 蓝田_Loto | 来源:发表于2021-05-10 10:48 被阅读0次

本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。


本文相关目录:
========== 所属文集:【Java】07_JavaWeb_Framework(开源框架) ==========
7.1_Mybatis3(概述、引入、API)
========== 所属文集:【Java】07_JavaWeb_Framework(开源框架) ==========


代码仓库:MyCode_JavaWeb_Framework


1、Mybatis的概述

1.1 对象/关系数据库映射(ORM)

  • ORM框架简介
- ORM 全称 Object/Relation Mapping,表示对象-关系映射的缩写

- ORM 完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后,程序员既可以利用面向
  对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。
  
- ORM把关系数据库包装成面向对象的模型。

- ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。

- 采用ORM框架后,应用程序不再直接访问底层数据库,而是以面向对象的方式来操作持久化对象,而ORM框架则将这些面向对象的操作转换成底层SQL操作。

- ORM框架实现的效果:把对持久化对象的保存、修改、删除等操作,转换为对数据库的操作
  • ORM分类
名称 特征 描述
Hibernate 全自动 不需要写SQL
mybatis 半自动 手动、自动一体,支持简单的映射,复杂关系要自己写SQL
Spring JDBC 纯手动 所有SQL需要自己写,有一套标准流程

1.2 Mybatis简介

- MyBatis是一款优秀的基于ORM的半自动轻量级持久层框架,它支持定制化SQL、存储过程以及高级映射,对JDBC操作数据库的过程进行封装
- MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO (Plain Old Java Objects,普通老式Java对 象)为数据库中的记录 
- Mybatis 通过 XML 或注解的方式将要执行的各种 statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过 Java 对象和 statement中 的 SQL 进行映射生成最终执行的 SQL语句,最后由 Mybatis 框架执行 SQL 并将结果映射成 Java 对象并返回

1.3 MyBatis 发展历史

- MyBatis 本来是 apache 的一个开源项目 iBatis
- iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架
- iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO) 
    
- 2010年6月,开源项目 iBatis 由 apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis
    
- 2013年11月代码迁移到 Github

1.4 MyBatis 优点

- Mybatis是一个半自动化的持久层框架,对开发人员开说,核心sql还是需要自己进行优化,sql和java编码进行分离,功能边界清晰,一个专注业务,一个专注数据 
    
- 使开发者只需要关注 SQL 本身,而不需要花费精力去处理 JDBC 繁杂的过程代码(如:注册驱动、创建connection、创建statement、手动设置参数、结果集检索等)

1.5 MyBatis 源码下载与概览

lib文件夹:mybatis的依赖包所在
mybatis-xxx.jar:mybatis的核心包
mybatis-xxx.pdf:mybatis使用手册

1.6【区别】Mybatis & Hibernate

区别 Mybatis Hibernate
本质上 半自动 全自动
安全性 存在SQL注入问题 预编译
日志系统
移植性
灵活性

(1)本质上

- Hibernate是全自动:Hibernate可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构,从而自动生成SQL语句
- MyBatis是半自动:MyBatis仅有基本的字段映射,对象数据和对象实际关系仍然需要通过定制SQL语句来实现和管理

- 对数据库类型进行切换时,Hibernate的成本明显低于MyBatis

(2)日志系统:Hibernate >> MyBatis

- Hibernate拥有完整的日志系统(如:SQL记录、关系异常、优化警告、缓存提示、脏数据警告等)
- MyBatis则除了基本记录功能外,功能薄弱很多

(3)移植性:Hibernate >> MyBatis

- Hibernate通过它强大的映射结构和HQL语言,大大降低了对象与数据库(Oracle、MySQL等)的耦合性
- MyBatis由于需要手写SQL,因此与数据库的耦合性直接取决于程序员写SQL的方法,如果SQL不具通用性并且用了很多某些固定数据库特性的SQL语句,移植性也会随之降低很多,成本很高

(4)灵活性:MyBatis >> Hibernate

- SQL优化方面,MyBatis都是直接编写原生态SQL到 XML 里,可严格控制SQL执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发

- SQL优化方面,Hibernate的SQL很多都是自动生成的,无法直接维护
  - 在一些复杂的业务需求上,HQL也是有局限性的
  - Hibernate虽然也支持原生SQL,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便

(5)安全性

- Hibernate是预编译的
- MyBatis可能存在SQL注入问题

2、Mybatis的引入

2.1 原生 JDBC 写法

public static void main(String[] args) {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
  try {
    // 加载数据库驱动
    Class.forName("com.mysql.jdbc.Driver");
    
    // 通过驱动管理类获取数据库链接
    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
 
    // 定义sql语句?表示占位符
    String sql = "select * from user where username = ?";

    // 获取预处理statement
    preparedStatement = connection.prepareStatement(sql);

    // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
    preparedStatement.setString(1, "tom");

    // 向数据库发出sql执行查询,查询出结果集
    resultSet = preparedStatement.executeQuery();

    // 遍历查询结果集
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        String username = resultSet.getString("username");
        // 封装User
        user.setId(id);
        user.setUsername(username);
    }
        System.out.println(user);
    }
 }catch (Exception e) {
    e.printStackTrace();
 } finally {
    // 释放资源
    if (resultSet != null) {
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    if (preparedStatement != null) {
        try {
            preparedStatement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2.2 Mybatis解决JDBC编程的问题

  • 原生jdbc开发存在的问题如下
(1)数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能
- 解决:使用数据库连接池初始化连接资源,在 SqlMapConfig.xml 中配置数据连接池,使用连接池管理数据库链接

(2)Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码
- 解决:将SQL语句配置/抽取在 XXXXmapper.xml 文件中,从而与Java代码分离

(3)使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护
- 原因:SQL 语句的 where 条件不一定,可能多也可能少,占位符需要和参数一一对应
- 解决:Mybatis自动将Java对象映射到SQL语句,通过 statement 中的parameterType定义输入参数类型

(4)对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便
- 原因:SQL 变化导致解析代码变化,并且解析前需要遍历
- 解决:使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射(Mybatis 自动将 SQL 执行结果映射到 Java对象,通过statement中的resultType定义输出结果类型)

2.3 自定义框架设计

(1)使用端:提供核心配置文件

sqlMapConfig.xml : 存放数据库配置信息,引入mapper.xml
Mapper.xml : 存放sql语句的配置文件信息(包含sql语句、参数类型、返回值类型)

(2)框架端:本质是对 JDBC 代码进行封装

(1)读取配置文件
- 根据配置文件的路径,加载配置文件成字节输入流,存储在内存中

(2)创建javaBean(容器对象)存放对配置文件解析出来的内容
- 读取完成以后配置信息以流的形式存在,如果存放在内存中,不好操作,所以创建容器对象
- Configuration 核心配置类: 存放sqlMapConfig.xml解析出来的内容(包含数据库基本信息、Map<唯一标识,Mapper> )
- 唯一标识:namespace + "." + id
- MappedStatement映射配置类:存放Mapper.xml解析出来的内容(包含sql语句、statement类型、输入参数java类型、输出参数java类型)

(3)解析配置文件
- 创建sqlSessionFactoryBuilder类  方法:sqlSessionFactory build(inputSteam in):
- 使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement容器对象中
- 创建 SqlSessionFactory 对象,获取sqlSession接口的实现类实例对象(工厂模式)

(4)创建 SqlSessionFactory 接口及实现类DefaultSqlSessionFactory
- openSession()方法用来生成sqlSession

(5)创建 SqlSession 接口及实现类 DefaultSession,定义对数据库的CRUD操作(封装JDBC完成对数据库表的查询操作)
selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
update()
delete()

(6)创建 Executor 接口及实现类 SimpleExecutor实现类
query(Configuration,MapperStatement,Object...params):执行JDBC代码

涉及到的设计模式:
Builder构建者设计模式、工厂模式、代理模式

2.4 自定义框架实现

使用端

步骤0:pom.xml依赖包
 <!--引入自定义持久层框架的依赖-->
<dependencies>
    <dependency>
        <groupId>com.loto.mybatis</groupId>
        <artifactId>mybatis2-custom</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <!--简化bean代码的⼯具包-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.18</version>
    </dependency>
</dependencies>
步骤1:创建配置文件 sqlMapConfig.xml
<configuration>
    <!-- 数据库配置信息 -->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///JavaWeb_7.1_Mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>

    <!-- 存放mapper.xml的全路径 -->
    <mapper resource="UserMapper.xml"/>
</configuration>
步骤2:UserMapper.xml
<mapper namespace="com.loto.mybatis.dao.IUserDao">
    <!-- sql的唯一标识 statementId:namespace.id-->

    <select id="findByCondition" resultType="com.loto.mybatis.pojo.User" parameterType="com.loto.mybatis.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

    <select id="findAll" resultType="com.loto.mybatis.pojo.User">
        select * from user
    </select>
</mapper>
步骤3:User.java实体
package com.loto.mybatis.pojo;

import lombok.Data;

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

步骤4:测试类
package com.loto.mybatis.test;

import com.loto.mybatis.dao.IUserDao;
import com.loto.mybatis.io.Resources;
import com.loto.mybatis.pojo.User;
import com.loto.mybatis.sqlsession.SqlSession;
import com.loto.mybatis.sqlsession.SqlSessionFactory;
import com.loto.mybatis.sqlsession.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class Test_MybatisCustom {
    /**
     * 查询所有
     */
    @Test
    public void test_findAll() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        List<User> users = sqlSession.selectList("com.loto.mybatis.dao.IUserDao.findAll");
        for (User user1 : users) {
            System.out.println(user1);
        }
    }

    /**
     * 根据条件查询
     */
    @Test
    public void test_findByCondition() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 调用
        User user = new User();
        user.setId(1);
        user.setUsername("TD");

        // sql的唯一标识 statementId:namespace.id
        User user2 = sqlSession.selectOne("com.loto.mybatis.dao.IUserDao.findByCondition", user);
        System.out.println(user2);
    }

    /**
     * 通过 Dao 执行方法
     */
    @Test
    public void test_getMapper() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        List<User> all = userDao.findAll();
        for (User user1 : all) {
            System.out.println(user1);
        }
    }
}

框架端

步骤0:pom.xml依赖包
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
    </dependency>

    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>

    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
    
    <!--简化bean代码的⼯具包-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.18</version>
    </dependency>
</dependencies>
步骤1:Configuration.java
package com.loto.mybatis.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

public class Configuration {
    private DataSource dataSource;

    /**
     * key:statementid <p>
     * value:封装好的mappedStatement对象
     */
    Map<String, MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }
}
步骤2:MappedStatement.java
package com.loto.mybatis.pojo;

import lombok.Data;

@Data
public class MappedStatement {
    /**
     * id标识
     */
    private String id;

    /**
     * 返回值类型
     */
    private String resultType;

    /**
     * 参数值类型
     */
    private String paramterType;

    /**
     * sql语句
     */
    private String sql;
}
步骤3:Resources.java
package com.loto.mybatis.io;

import java.io.InputStream;

public class Resources {
    /**
     * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
     */
    public static InputStream getResourceAsSteam(String path) {
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}
步骤4:SqlSessionFactoryBuilder.java
package com.loto.mybatis.sqlsession;

import com.loto.mybatis.config.XMLConfigBuilder;
import com.loto.mybatis.pojo.Configuration;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        // 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        // 第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }
}
步骤5:XMLConfigBuilder.java
package com.loto.mybatis.config;

import com.loto.mybatis.io.Resources;
import com.loto.mybatis.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

public class XMLConfigBuilder {
    private Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 使用dom4j对配置文件进行解析,封装成 Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);

        //<configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        }

        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        configuration.setDataSource(comboPooledDataSource);

        // mapper.xml解析: 拿到路径 -- 字节输入流 -- dom4j进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        }

        return configuration;
    }
}
步骤6:XMLMapperBuilder.java
package com.loto.mybatis.config;

import com.loto.mybatis.pojo.Configuration;
import com.loto.mybatis.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

public class XMLMapperBuilder {
    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");

        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sqlText = element.getTextTrim();

            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(parameterType);
            mappedStatement.setSql(sqlText);
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key, mappedStatement);
        }
    }
}
其他文件参见代码仓库

3、Mybatis的API

3.1 SqlSessionFactoryBuilder(SqlSession 工厂构建器)

常用API:SqlSessionFactory build(InputStream inputStream)
作用:通过加载 mybatis 的核心文件的输入流的形式构建一个 SqlSessionFactory 对象

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

Resources 工具类
- 在 org.apache.ibatis.io 包中
- 作用:Resources 类帮助你从类路径下、文件系统或一个 web URL 中加载资源文件

3.2 SqlSessionFactory (SqlSession工厂对象)

  • SqlSessionFactory 有多个方法创建 SqlSession 实例,常用的有如下两个
方法 解释
openSession() 默认开启一个事务,不会自动提交(需手动提交事务,更新操作数据才会持久化到数据库)
openSession(boolean autoCommit) 参数为是否自动提交,如设置为True,则不需要手动提交事务

3.3 Session(会话对象)

  • 执行语句的方法
<T> T selectOne(String statement, Object parameter) 
<E> List<E> selectList(String statement, Object parameter)
    
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

  • 操作事务的方法
void commit() 
void rollback()

作者:蓝田_Loto
【平台来源】

简书

【代码托管平台】

Github

【如有疑问,请通过以下方式交流】

评论区回复
发送邮件shorfng@126.com


本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,谢谢合作。

相关文章

网友评论

      本文标题:【Java】07_JavaWeb_Framework(开源框架)

      本文链接:https://www.haomeiwen.com/subject/rbgbdltx.html