SSM源码分析之Spring11-手写SpringORM

前言

使用 Spring 进行基本的 JDBC 访问数据库有多种选择。Spring 至少提供了三种不同的工作模式: JdbcTemplate, 一个在 Spring2.5 中新提供的 SimpleJdbc 类能够更好的处理数据库元数据; 还有一种称之为 RDBMS Object 的风格的面向对象封装方式, 有点类似于 JDO 的查询设计。 我们在这里简要列举你采取某一种工作方式的主要理由. 不过请注意, 即使你选择了其中的一种工作模式, 你依然可以在你的代码中混用其他任何一种模式以获取其带来的好处和优势。 所有的工作模式都必须要求 JDBC 2.0 以上的数据库驱动的支持, 其中一些高级的功能可能需要 JDBC 3.0 以上的数据库驱动支持。

JdbcTemplate - 这是经典的也是最常用的 Spring 对于 JDBC 访问的方案。这也是最低级别的封装, 其他的工作模式事实上在底层使用了 JdbcTemplate 作为其底层的实现基础。JdbcTemplate 在 JDK 1.4 以上的环境上工作得很好。

NamedParameterJdbcTemplate - 对 JdbcTemplate 做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的 JDBC 所使用的“?”作为参数的占位符。这种方式在你需要为某个 SQL 指定许多个参数时,显得更加直观而易用。该特性必须工作在 JDK 1.4 以上。

SimpleJdbcTemplate - 这个类结合了 JdbcTemplate 和 NamedParameterJdbcTemplate 的最常用的功能,同时它也利用了一些 Java 5 的特性所带来的优势,例如泛型、varargs 和 autoboxing 等,从而提供了更加简便的 API 访问方式。需要工作在 Java 5 以上的环境中。

SimpleJdbcInsert 和 SimpleJdbcCall - 这两个类可以充分利用数据库元数据的特性来简化配置。

通过使用这两个类进行编程,你可以仅仅提供数据库表名或者存储过程的名称以及一个 Map 作为参数。

其中 Map 的 key 需要与数据库表中的字段保持一致。这两个类通常和 SimpleJdbcTemplate 配合使用。

这两个类需要工作在 JDK 5 以上,同时数据库需要提供足够的元数据信息。

RDBMS 对象包括 MappingSqlQuery, SqlUpdate and StoredProcedure - 这种方式允许你在初始

化你的数据访问层时创建可重用并且线程安全的对象。该对象在你定义了你的查询语句,声明查询参数并编译相应的 Query 之后被模型化。一旦模型化完成,任何执行函数就可以传入不同的参数对之进行多次调用。这种方式需要工作在 JDK 1.4 以上。

SpringJDBC

异常处理

异常结构如下:
在这里插入图片描述

SQLExceptionTranslator 是 一 个 接 口 , 如 果 你 需 要 在 SQLException 和 org.springframework.dao.DataAccessException 之间作转换,那么必须实现该接口。 转换器类的实现可以采用一般通用的做法(比如使用 JDBC 的 SQLState code),如果为了使转换更准确,也可以进行定制(比如使用 Oracle 的 error code)。

SQLErrorCodeSQLExceptionTranslator 是 SQLExceptionTranslator 的默认实现。 该实现使用指定数据库厂商的 error code,比采用 SQLState 更精确。转换过程基于一个 JavaBean(类型为SQLErrorCodes)中的 error code。 这个 JavaBean 由 SQLErrorCodesFactory 工厂类创建,其中
的内容来自于 “sql-error-codes.xml”配置文件。该文件中的数据库厂商代码基于 Database MetaData 信息中的 DatabaseProductName,从而配合当前数据库的使用。

SQLErrorCodeSQLExceptionTranslator 使用以下的匹配规则:

首先检查是否存在完成定制转换的子类实现。通常 SQLErrorCodeSQLExceptionTranslator 这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。

接着将 SQLException 的 error code 与错误代码集中的 error code 进行匹配。 默认情况下错误代码集将从 SQLErrorCodesFactory 取得。 错误代码集来自 classpath 下的 sql-error-codes.xml 文件,它们将与数据库 metadata 信息中的 database name 进行映射。

使用 fallback 翻译器。SQLStateSQLExceptionTranslator 类是缺省的 fallback 翻译器。

config 模块

NamespaceHandler 接口,DefaultBeanDefinitionDocumentReader 使用该接口来处理在 spring xml 配置文件中自定义的命名空间。
在这里插入图片描述
在jdbc 模块,我们使用 JdbcNamespaceHandler 来处理 jdbc 配置的命名空间,其代码如下:·

public class JdbcNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
	registerBeanDefinitionParser("embedded-database", new EmbeddedDatabaseBeanDefinitionParser()); 
	registerBeanDefinitionParser("initialize-database", new InitializeDatabaseBeanDefinitionParser());
	}
}

其中,EmbeddedDatabaseBeanDefinitionParser 继承了 AbstractBeanDefinitionParser,解析 < embedded-database>元素,并使用 EmbeddedDatabaseFactoryBean 创建一个 BeanDefinition。顺便介绍一下用到的软件包 org.w3c.dom。

软件包 org.w3c.dom:为文档对象模型 (DOM) 提供接口,该模型是 Java API for XML Processing 的组件 API。该 Document Object Model Level 2 Core API 允许程序动态访问和更新文档的内容和结构。

Attr:Attr 接口表示 Element 对象中的属性。

CDATASection: CDATA 节用于转义文本块,该文本块包含的字符如果不转义则会被视为标记。

CharacterData: CharacterData 接口使用属性集合和用于访问 DOM 中字符数据的方法扩展节点。

Comment: 此接口继承自 CharacterData 表示注释的内容,即起始 ‘ ’ 之间的所有字符。

Document: Document 接口表示整个 HTML 或 XML 文档。

DocumentFragment: DocumentFragment 是“轻量级”或“最小”Document 对象。

DocumentType : 每个 Document 都有 doctype 属性,该属性的值可以为 null ,也可以为DocumentType 对象。

DOMConfiguration: 该 DOMConfiguration 接口表示文档的配置,并维护一个可识别的参数表。

DOMError: DOMError 是一个描述错误的接口。

DOMErrorHandler: DOMErrorHandler 是在报告处理 XML 数据时发生的错误或在进行某些其他处理(如验证文档)时 DOM 实现可以调用的回调接口。

DOMImplementation: DOMImplementation 接口为执行独立于文档对象模型的任何特定实例的操作提供了许多方法。

DOMImplementationList: DOMImplementationList 接口提供对 DOM 实现的有序集合的抽象,没有定义或约束如何实现此集合。

DOMImplementationSource:此接口允许 DOM 实现程序根据请求的功能和版本提供一个或多个实现,如下所述。

DOMLocator: DOMLocator 是一个描述位置(如发生错误的位置)的接口。

DOMStringList: DOMStringList 接口提供对 DOMString 值的有序集合的抽象,没有定义或约束此集合是如何实现的。
Element: Element 接口表示 HTML 或 XML 文档中的一个元素。

Entity: 此接口表示在 XML 文档中解析和未解析的已知实体。

EntityReference: EntityReference 节点可以用来在树中表示实体引用。

NamedNodeMap: 实现 NamedNodeMap 接口的对象用于表示可以通过名称访问的节点的集合。 NameList NameList 接口提供对并行的名称和名称空间值对(可以为 null 值)的有序集合的抽象,无需定义或约束如何实现此集合。

Node: 该 Node 接口是整个文档对象模型的主要数据类型。

NodeList: NodeList 接口提供对节点的有序集合的抽象,没有定义或约束如何实现此集合。

Notation: 此接口表示在 DTD 中声明的表示法。

ProcessingInstruction: ProcessingInstruction 接口表示“处理指令”,该指令作为一种在文档的文本中保持特定于处理器的信息的方法在 XML 中使用。

Text: 该 Text 接口继承自 CharacterData,并且表示 Element 或 Attr 的文本内容(在 XML 中称为 字符数据)。

TypeInfo: TypeInfo 接口表示从 Element 或 Attr 节点引用的类型,用与文档相关的模式指定。

UserDataHandler: 当使用 Node.setUserData() 将一个对象与节点上的键相关联时,当克隆、导入或重命名该对象关联的节点时应用程序可以提供调用的处理程序。

core 模块

JdbcTeamplate

在这里插入图片描述

RowMapper

在这里插入图片描述

元数据 metaData 模块

本节中 spring 应用到工厂模式,结合代码可以更具体了解。
在这里插入图片描述
CallMetaDataProviderFactory 创建 CallMetaDataProvider 的工厂类,其代码如下:

public static final List<String> supportedDatabaseProductsForProcedures = Arrays.asList( "Apache Derby",

"DB2",

"MySQL",

"Microsoft SQL Server",

"Oracle",

"PostgreSQL",

"Sybase"

);
/** List of supported database products for function calls */

public static final List<String> supportedDatabaseProductsForFunctions = Arrays.asList( "MySQL",
"Microsoft SQL Server",

"Oracle",

"PostgreSQL"

);


static public CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {
try {

CallMetaDataProvider result = (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName()); boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData(); if (context.isFunction()) {

if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) { if (logger.isWarnEnabled()) {
logger.warn(databaseProductName + " is not one of the databases fully supported for function calls

"+

"-- supported are: " + supportedDatabaseProductsForFunctions);

}

if (accessProcedureColumnMetaData) {

logger.warn("Metadata processing disabled - you must specify all parameters explicitly"); accessProcedureColumnMetaData = false;
}

}

}

else {

if (!supportedDatabaseProductsForProcedures.contains(databaseProductName)) { if (logger.isWarnEnabled()) {
logger.warn(databaseProductName + " is not one of the databases fully supported for procedure

calls " +

"-- supported are: " + supportedDatabaseProductsForProcedures);

}

if (accessProcedureColumnMetaData) {

logger.warn("Metadata processing disabled - you must specify all parameters explicitly"); accessProcedureColumnMetaData = false;
}

}

}

CallMetaDataProvider provider;

if ("Oracle".equals(databaseProductName)) {

provider = new OracleCallMetaDataProvider(databaseMetaData);
}

else if ("DB2".equals(databaseProductName)) {

provider = new Db2CallMetaDataProvider((databaseMetaData));

}

else if ("Apache Derby".equals(databaseProductName)) {

provider = new DerbyCallMetaDataProvider((databaseMetaData));

}

else if ("PostgreSQL".equals(databaseProductName)) {

provider = new PostgresCallMetaDataProvider((databaseMetaData));

}

else if ("Sybase".equals(databaseProductName)) {

provider = new SybaseCallMetaDataProvider((databaseMetaData));

}

else if ("Microsoft SQL Server".equals(databaseProductName)) { provider = new SqlServerCallMetaDataProvider((databaseMetaData));
}

else if ("HDB".equals(databaseProductName)) {

provider = new HanaCallMetaDataProvider((databaseMetaData));

}

else {

provider = new GenericCallMetaDataProvider(databaseMetaData);

}

if (logger.isDebugEnabled()) {

logger.debug("Using " + provider.getClass().getName());

}

provider.initializeWithMetaData(databaseMetaData);

if (accessProcedureColumnMetaData) {

provider.initializeWithProcedureColumnMetaData(databaseMetaData,

context.getCatalogName(), context.getSchemaName(), context.getProcedureName());

}

return provider;

});

return result;

}

catch (MetaDataAccessException ex) {

throw new DataAccessResourceFailureException("Error retrieving database metadata", ex);

}

}

TableMetaDataProviderFactory 创建 TableMetaDataProvider 工厂类,其创建过程如下:

static public CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext

context) {

try {

CallMetaDataProvider result = (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName()); boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData(); if (context.isFunction()) {

if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) { if (logger.isWarnEnabled()) {
logger.warn(databaseProductName + " is not one of the databases fully supported for function calls

" +

"-- supported are: " + supportedDatabaseProductsForFunctions);

}

if (accessProcedureColumnMetaData) {

logger.warn("Metadata processing disabled - you must specify all parameters explicitly"); accessProcedureColumnMetaData = false;
}

}

}

else {

if (!supportedDatabaseProductsForProcedures.contains(databaseProductName)) { if (logger.isWarnEnabled()) {
logger.warn(databaseProductName + " is not one of the databases fully supported for procedure

calls " +

"-- supported are: " + supportedDatabaseProductsForProcedures);

}

if (accessProcedureColumnMetaData) {

logger.warn("Metadata processing disabled - you must specify all parameters explicitly"); accessProcedureColumnMetaData = false;
}

}

}

CallMetaDataProvider provider;

if ("Oracle".equals(databaseProductName)) {

provider = new OracleCallMetaDataProvider(databaseMetaData);

}

else if ("DB2".equals(databaseProductName)) {

provider = new Db2CallMetaDataProvider((databaseMetaData));

}

else if ("Apache Derby".equals(databaseProductName)) {

provider = new DerbyCallMetaDataProvider((databaseMetaData));

}

else if ("PostgreSQL".equals(databaseProductName)) {

provider = new PostgresCallMetaDataProvider((databaseMetaData));

}

else if ("Sybase".equals(databaseProductName)) {

provider = new SybaseCallMetaDataProvider((databaseMetaData));

}

else if ("Microsoft SQL Server".equals(databaseProductName)) { provider = new SqlServerCallMetaDataProvider((databaseMetaData));
}

else if ("HDB".equals(databaseProductName)) {

provider = new HanaCallMetaDataProvider((databaseMetaData));

}

else {

provider = new GenericCallMetaDataProvider(databaseMetaData);

}

if (logger.isDebugEnabled()) {

logger.debug("Using " + provider.getClass().getName());

}

provider.initializeWithMetaData(databaseMetaData);

if (accessProcedureColumnMetaData) {

provider.initializeWithProcedureColumnMetaData(databaseMetaData,

context.getCatalogName(), context.getSchemaName(), context.getProcedureName());

}

return provider;

});

return result;

}

catch (MetaDataAccessException ex) {

throw new DataAccessResourceFailureException("Error retrieving database metadata", ex);

}

}

使用 SqlParameterSource 提供参数值

使用 Map 来指定参数值有时候工作得非常好,但是这并不是最简单的使用方式。Spring 提供了一些其他 的 SqlParameterSource 实 现 类 来 指 定 参 数 值 。 我 们 首 先 可 以 看 看

BeanPropertySqlParameterSource 类,这是一个非常简便的指定参数的实现类,只要你有一个符合 JavaBean 规范的类就行了。它将使用其中的 getter 方法来获取参数值。

SqlParameter 封 装 了 定 义 sql 参 数 的 对 象 。 CallableStateMentCallback , PrePareStateMentCallback , StateMentCallback , ConnectionCallback 回 调 类 分 别 对 应 JdbcTemplate 中的不同处理方法。

在这里插入图片描述

simple 实现

在这里插入图片描述

DataSource

spring 通过 DataSource 获取数据库的连接。Datasource 是 jdbc 规范的一部分,它通过 ConnectionFactory 获取。一个容器和框架可以在应用代码层中隐藏连接池和事务管理。

当使用 spring 的 jdbc 层,你可以通过 JNDI 来获取 DataSource,也可以通过你自己配置的第三方连接池实现来获取。流行的第三方实现由 apache Jakarta Commons dbcp 和 c3p0.

在这里插入图片描述
TransactionAwareDataSourceProxy 作为目标 DataSource 的一个代理, 在对目标 DataSource 包装的同时,还增加了 Spring 的事务管理能力, 在这一点上,这个类的功能非常像 J2EE 服务器所提供的事务化的 JNDI DataSource。

Note

该类几乎很少被用到,除非现有代码在被调用的时候需要一个标准的 JDBC DataSource 接口实现作为参数。 这种情况下,这个类可以使现有代码参与 Spring 的事务管理。通常最好的做法是使用更高层的抽象 来对数据源进行管理,比如 JdbcTemplate 和 DataSourceUtils 等等。

注意:DriverManagerDataSource 仅限于测试使用,因为它没有提供池的功能,这会导致在多个请求获取连接时性能很差。

object 模块

在这里插入图片描述

JdbcTemplate是 core包的核心类

它替我们完成了资源的创建以及释放工作,从而简化了我们对 JDBC 的使用。 它还可以帮助我们避免一些常见的错误,比如忘记关闭数据库连接。 JdbcTemplate 将完成 JDBC 核心处理流程,比如 SQL 语句的创建、执行,而把 SQL 语句的生成以及查询结果的提取工作留给我们的应用代码。 它可以完成 SQL 查询、更新以及调用存储过程,可以对 ResultSet 进行遍历并加以提取。 它还可以捕获 JDBC 异常并将其转换成 org.springframework.dao 包中定义的,通用的,信息更丰富的异常。

使 用 JdbcTemplate 进 行 编 码 只 需 要 根 据 明 确 定 义 的 一 组 契 约 来 实 现 回 调 接 口 。 PreparedStatementCreator 回调接口通过给定的 Connection 创建一个 PreparedStatement,包含 SQL 和任何相关的参数。 CallableStatementCreateor 实现同样的处理,只不过它创建的是CallableStatement。 RowCallbackHandler 接口则从数据集的每一行中提取值。

我们可以在 DAO 实现类中通过传递一个 DataSource 引用来完成 JdbcTemplate 的实例化,也可以在 Spring 的 IOC 容器中配置一个 JdbcTemplate 的 bean 并赋予 DAO 实现类作为一个实例。需要注意的

是DataSource 在 Spring 的 IOC 容器中总是配制成一个 bean,第一种情况下,DataSource bean 将

传递给 service,第二种情况下 DataSource bean 传递给 JdbcTemplate bean。

7.NamedParameterJdbcTemplate 类为 JDBC 操作增加了命名参数的特性支持,而不是传统的使用(’?’)作为参数的占位符。NamedParameterJdbcTemplate 类对 JdbcTemplate 类进行了封装, 在底层,JdbcTemplate 完成了多数的工作。

开发ORM框架

ORM(对象关系映射Object Relation Mapping)。说的就是讲已经持久化的数据内容转换为一个Java对象,用Java对象来描述对象与对象之间的关系和数据内容。
常见的ORM框架:

  • Hibernate
  • MyBatis
  • JPA
  • SpringJDBC

Hibernate 全自动档 不需要写一句SQL语句、烧油(牺牲性能)
MyBatis 手自一体(半自动) 支持单表映射,多表关联需要配置,轻量级一些
SpringJDBC 手动挡 包括SQL语句,映射都是要自己实现的(最省油的)
喜欢轻装上阵 Spring本来就是万能胶IOC/AOP/DI/MVC/JDBC/BigData/Cloud/Boot, 因为Spring形成一个生态

为什么要自己写ORM框架?

  • 解决实际的业务问题(根据业务需要)
  • 自定义需求,如果要直接第三方开源框架的话,需要进行二次开发
  • 解决团队成员之间水平参差不齐的问题
  • 可以实现统一的管理、监控、排错等等一系列底层操作

实际场景应用

以某平台大数据检测系统为例:
痛点:

  • 数据吞吐量大
  • 数据存储方式多样化
  • 数据源需要频繁切换
  • API无法统一
    系统架构图:
    在这里插入图片描述

通过实现最底层的类解决问题:

1、统一方法名
select
insert
delete
update

find/get/load/query

//约定
如果是删、改,以ID作为唯一的检索条件,如果没有ID,那么要先查出来得到ID

2、统一参数
如果是做条件查询 QueryRule(自己封装)
批量更新和插入 ,方法名以All结尾 参数为List
删、改、插一条数据 ,参数用 T

3、统一返回结果

所有的分页操作返回Page

所有的集合查询返回 List

所有的单条查询返回 T

所有的ID采用Long类型

所有的删除、修改、增加操作返回boolean

对外输出都用ResultMsg

4、 统一封装Freamwork
java extends core common
javax.core.common.utils 操作工具包
javax.core.common.config 统一配置
javax.core.common.doc 文档生成

javax.core.common.jdbc JDBC依赖
javax.core.common.redis Redis
javax.core.common.mongodb MongoDB
javax.core.common.hbase Hbase

只要是Spring相关的配置都以 application- 开头
建议不要把所有的东西在一个文件中,这样不便于团队开发的维护

aop 配置切面,代理规则的
beans 配置单例对象的
common 配置通用的配置
context 主入口
db 数据库相关
web 跟页面打交道的、拦截器、过滤器、监听器、模板

在这里插入图片描述
具体的工具类读者可以在本文末给出的Github地址获取

实现ORM操作

public class JdbcTest {

    public static void main(String[] args) {

//        List<?> result = select(Member.class);
        Member condition = new Member();
        condition.setName("tom");
//        condition.setAge(20);
        List<?> result = select(condition);

        System.out.println(Arrays.toString(result.toArray()));

    }

    //我框架问世的时候,你的Member类都还没有从石头缝里蹦出来
    private static List<?> select(Object condition){
        try{


            Class<?> entityClass = condition.getClass();

            //1、加载驱动类
            Class.forName("com.mysql.jdbc.Driver");

            //2、建立连接
            Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/gupaoedu_demo?characterEncoding=UTF-8&rewriteBatchedStatements=true","root","123456");

            Table table = entityClass.getAnnotation(Table.class);


            //3、创建语句开始事务
            //为了简便,暂时用select * from 代替,不要说我不严谨,OK?
            String sql = "select * from " + table.name();
            StringBuffer where = new StringBuffer(" where 1=1 ");
            Field[] fields = entityClass.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = field.get(condition);
                if(null != value) {
                    Class<?> clazz = field.getType();
                    if(String.class == clazz){
                        where.append(" and " + field.getName() + " = '" + value + "'");
                    }else{
                        where.append(" and " + field.getName() + " = " + value);
                    }

                }
            }


            System.out.println(sql + where.toString());

            PreparedStatement pstmt = conn.prepareStatement(sql + where.toString());

            //4、执行语句集
            ResultSet rs = pstmt.executeQuery();

            //5、获取结果集

            //ORM:数据表中的记录要复制到Java的Object中
            //ORM原理:反射机制
            //自动赋值
            //拿到一共有多少个列
            List<Object> result = new ArrayList<Object>();

            int columnCount = rs.getMetaData().getColumnCount();
            while (rs.next()){ //游标


                //===========Begin ORM ============
                Object instance = entityClass.newInstance();
                for (int i = 1; i <= columnCount; i ++) {
                    String columnName = rs.getMetaData().getColumnName(i);
                    Field field = entityClass.getDeclaredField(columnName);
                    field.setAccessible(true);

                    //数据类型映射非常关键
//                    Object type = field.getType();
//                    if(type == Long.class){
//                        field.set(instance,rs.getLong(columnName));
//                    }else if(String.class == type){
//                        field.set(instance,rs.getString(columnName));
//                    }else if(Integer.class == type){
//                        field.set(instance,rs.getInt(columnName));
//                    }

                    field.set(instance,rs.getObject(columnName));


                    //各自的厂商实现自己的链接
                    //MySQL为例,以下类型Java语言中是不存在的
                    //bigint ,由开发厂商自动就映射好了
                    //varchar
                    //int
//                    System.out.println(rs.getObject(columnName).getClass());


                }
                //===========End ORM ==============
                result.add(instance);
            }


            //System.out.println(Arrays.toString(result.toArray()));




            //6、关闭结果集、关闭语句集、关闭连接
            rs.close();
            pstmt.close();
            conn.close();

            return  result;
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            //关闭资源要在Finally块中
        }
        return  null;
    }


}

Member类:

@Entity
@Data
@Table(name="t_member")
public class Member implements Serializable{

    @Id private Long id;
    private String name;
    private String addr;
    private Integer age;
}

手写ORM框架升级

基于Spring5实现ORM
初衷:

  • 单表查询不写一句SQL,自动生成
  • 查询的结果自动映射为Java对象

1、我要传一个复杂的查询条件,怎么传?
想到了用对象来传,但是有问题
a)、跨表联查的条件
b)、无法携带判断逻辑的运算符
c)、or 或者 and 无法区分

2、自动映射类型判断麻烦

用rs.getObject()方法

3、跨多种数据库如何统一API

4、数据源的动态切换如何来做?
AbstractRoutingDataSource数据源自动路由,执行数据连接以前,根据业务规则来动态获取到一个连接
从而达到动态切换数据库连接的效果
为了操作方便,每次执换完,执行成以后,会将数据源恢复到默认的设置

5、SQL注入
1、首先将QueryRule构建好,把所有的查询条件保存到一个ruleList中
2、再写一个工具类QueryRuleSqlBuilder,循环ruleList对每一个条件分别处理processAnything,主要是构建where后面语句
3、process以后propertisList 保存诸如 and name = ? , values tom,利用索引位置相同,来进行一一对应

根据以上问题,实际解决如下:
在这里插入图片描述
framework作为我们封装的自定义ORM框架
demo作为数据持久化层
javax.core.common作为公共基础包
resources作为配置文件
test作为测试类
由于篇幅过长,我讲本节代码地址放到下方,并且加以注释说明,读者可以自行测试

疑问解析

问:selectbysql就相当于没有使用框架,不符合之前统一参数,统一查询方法拉?
答:不推荐使用,如果一定要多表查询,只有两种方案
a) 写SQL语句
b) 查多次,在内存中处理数据关系,一般会在Service中进行处理
在Java代码中处理,会让程序员更加容易理解
如果给大串SQL,这后来接手的人直接想死
我见过一条SQL语句有10行之长的,我直接看晕了
----------- 在我的团队中,极少数使用多表关联查询 -------------

问:······这个PK主键传过来怎么用?有什么好处
答:<T extends Serializable,PK extends Serializable> 传于不传不影响功能
目的:返回结果不需要再手动的强制类型转换

问:如用老师这个框架来组装sql,复杂查询的话,会不会难以组装,
比如说查某个表的字段是另外一个表的条件,以此类推多个表的话?
答:这就是属于多表查询

问:连接操作,还有如果表没有主键PK 还需要传?
答:只要用的我框架,每个表必须有主键,哪怕是自增
为了降低程序设计的复杂度

后记

springORM github代码地址
本系列参考的Spring源码版本是:Spring 5.0.2.RELEASE

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、SBORM 介绍 1、目前只考虑支持 mysql; 2、基于spring jdbc的上层封装,底层jdbc操作基于JdbcTemplate,对于使用spring jdbc的人会有一点价值,比较简洁的封装可以节省很多重复劳动,具体节省多少可以看看example; 3、实现一套简单的ORM(直接使用spring rowmapper,insert自己实现),可以基于对象进行crud和相对复杂(感觉比hibernate强大一点)的sql操作; 4、基于对象指定查询的字段,大部分时候可以忘掉表结构进行业务开发; 5、支持简单的数据库路由,读写分离(半自动,需要指定取writer还是reader,默认规则reader采用随机的方式,当然也可以手动指定); 6、支持简单的分表,主要是针对一定规则的分表,比如百分表、千分表,也可以自己指定分表后缀; 7、简单的单表查询(比如所有条件是and或者or结构),基本实现0sql代码编写(类似HibernateTemplate selectByExample、findByCriteria、find等方法); 8、简单的单表排序支持,支持多个排序条件组合; 9、对于复杂的sql查询,提供获取jdbctemplate实例进行操作,类似spring jdbc的常规用法; 10、提供Entity代码生成接口,Entity并非简单的pojo(尽可能不要去修改此类),引入字段常量类,方便查询的时候指定选择字段,从而更好实现查询条件的封装; 二、为什么写SBORM? 1、hibernate:过于臃肿,使用不够灵活,优化难(其实主要是因为很少用),HQL感觉就是个渣,在 mysql几乎一统天下的背景下,跨数据库级别的兼容吃力不讨好。Hibernate的对象化关联处理确实挺强大,但是使用起来坑太多,有多少人敢在项目 大范围使用真不知道,屠龙刀不是人人都提的起啊。 2、mybatis:轻量级,基于xml的模式感觉不利于封装,代码量不小,基于xml维护也麻烦(个人观点, 现在注解模式貌似也挺不错),感觉mybatis更适合存在dba角色的年代,可以远离代码进行sql调优,复杂的查询拼装起来也更加优雅(java基本 就是if else ...),但是对于查询业务简单但是数据库集群环境的场景有点憋屈(其实对mybatis使用也不多,瞎评论^_^)。 3、spring jdbc:小巧,灵活,足够优秀,个人比较喜欢使用,但是代码量偏大,原生的接口重复劳动量大,比如insert、mapper之类的; SBORM只是针对spring jdbc的一些不方便的地方,做了一些封装,更加简化日常的开发工作,基于spring jdbc的RowMapper自动实现对象映射,也勉强算的上叫ORM,只是大部分功能已经由spring jdbc实现了。 平时不太喜欢使用hibernate和mybatis,主要是使用spring jdbc,写这个东西的出发点主要是平时使用spring jdbc觉 得比较麻烦,重复性的代码偏多,一方面通过自动mapper降低返回结果处理工作量,另一方面参考hibernate对象化查询条件的模式,写了一个 QueryBudiler,使得更多简单的单表查询可以通过对象组织查询、更改逻辑,避免过多去写相似性的SQL语句,减少DAO接口量。 三、一些亮点 1、Entity的设计:很多人看了也许会说,这个不是POJO,不是纯粹的Java Bean,显得很另类。但是有多人在开发过程(特别是在写sql的时候),经常要去看看表结构设计?还有多少次因为改了表某个字段,还得遍历去查找哪些 sql使用了这个字段?多少次看到在代码直接传入字段名作为查询参数感到别扭?如果将表结构字段都用java对象去描述,能够解决这些问题,就不必要在 乎是不是POJO了,后面看example的时候应该能体会这么做的一些好处,至少我觉得是挺方便的,将大部分查询脱离表结构设计。 2、简单的数据库路由:如果分库结构不是太复杂(比如简单的读写分离、或者多个库集成),BaseDao可以自 动进行路由(比如读写分离,根据业务模式指定读、写库),如果非默认的路由规则,也可以通过手动设置的模式,进行数据库路由。数据库路由直接由 Entity指定,所有的路由都是根据Entity识别,也就是说查询也是围绕Entity展开的,避免类似使用spring jdbc的时候,各种 template实例跳来跳去,硬编码引入,写一个业务还得看看到底该用哪个template,尤其是多个数据库共用一个template实例的时候。 3、QueryBuilder:单表查询基本上都可以实现零Sql(除非查询条件特别复杂的),更新、删除等操作也可以通过QueryBuilder进行批量处理,不局限于根据主键来处理。 4、分表操作的支持:对于分表操作和常规的使用没有区别,只是指定分表规则,mybatis好像也可以通过制定参数实现分表处理,没搞清楚hibernate对这个是怎么处理的(hibernate好像是bean和表一对一绑定的)? 标签:sborm

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值