盘点 JPA : SQL 解析 | Java Debug 笔记

2,110 阅读7分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看 活动链接

总文档 :文章目录
Github : github.com/black-ant

一 . 前言

JPA 使用过程中 , 经常会出现解析的异常 ,通过以下流程 , 有利于找到对应的节点

1.1 前置知识点

EntityManager

EntityManager 实例与持久性上下文关联。
持久性上下文是一组实体实例,其中对于任何持久性实体标识,都存在唯一的实体实例。在持久化上下文中,管理实体实例及其生命周期。EntityManager API 用于创建和删除持久性实体实例,根据实体的主键查找实体,以及查询实体。

persistent 简介

persistent 是持久化上下文 , 用于将事务持久化
持久性上下文处理一组实体,这些实体包含要在某个持久性存储中持久化的数据(例如数据库)。特别是,上下文知道一个实体在上下文和底层持久性存储方面可能具有的不同状态(例如托管、分离)。

persistent 功能

  • 持久性上下文通常通过 EntityManager 访问
  • 每个EntityManager实例都与一个持久化上下文相关联。
  • 在持久化上下文中,管理实体实例及其生命周期。
  • 持久化上下文定义了一个作用域,特定实体实例在这个作用域下被创建、持久化和删除。
  • 持久化上下文类似于包含一组持久化实体的缓存,因此一旦事务完成,所有持久化对象都将从EntityManager的持久化上下文中分离出来,不再进行管理。

persistent 的分类

  • Transaction-scoped persistence context
  • Extended-scoped persistence context

二 . 逻辑流程

2.1 SQL 解析主流程

主流程可以分为几个部分 :

Execute Action 入口流程

// Step 1 : ExecutableList 处理
C51- ActionQueue
    F51_01- LinkedHashMap<Class<? extends Executable>,ListProvider>
    M51_01- executeActions() : 执行所有当前排队的操作
        FOR- 循环所有的 ListProvider 
            - 通过当前 ListProvider 获取 ExecutableList , 调用 M51_02
    M51_02- executeActions(ExecutableList<E> list)
        FOR- 循环list , 调用 ExecutableList.execute() -> M52_01
 

// Step 2 : Entity 实体类的处理
C52- EntityInsertAction
    M52_01- execute()
        // Step 1 : 参数准备
        - EntityPersister persister = getPersister();
        - SharedSessionContractImplementor session = getSession();
        - Object instance = getInstance();
        - Serializable id = getId();
        // Step 2 : 执行 insert 流程
        - persister.insert( id, getState(), instance, session ) -> M53_01
        // Step 3 : insert 操作的执行
        - PersistenceContext persistenceContext = session.getPersistenceContext();
        - EntityEntry entry = persistenceContext.getEntry( instance )
        - entry.postInsert( getState() )
        //........ 后续操作在操作流程中再详细看 
        

sequenceDiagram
M54_01->>M54_02: statement 请求流程
M54_02->>M55_02: statement 创建
M55_02->>M54_01: 返回 statement
M54_01->>M54_03: 对 statement 再次处理
M54_03->>M56_01: 注册JDBC语句

Insert 操作准备流程 :

注意其中的核心逻辑 M54_01

其中主要是 Statement 的处理 , 请求和创建 :

  • AbstractEntityPersister # insert -> M53_01
  • AbstractEntityPersister # insert -> M53_02
  • StatementPreparerImpl # prepareStatement -> M54_01
  • StatementPreparerImpl # buildPreparedStatementPreparationTemplate -> M54_02
  • HikariProxyConnection # prepareStatement
  • ProxyConnection # prepareStatement
  • ConnectionImpl # prepareStatement -> M55_01
  • ConnectionImpl # clientPrepareStatement -> M55_02

postProcess 处理流程 , statement 注册

  • StatementPreparerImpl # postProcess -> M54_03 (M54_01 -> M54_03 )
  • ResourceRegistryStandardImpl # register -> M56_01
// Step 1 : 核心中间处理器    
C53- AbstractEntityPersister
    M53_01- insert(Serializable id, Object[] fields, Object object, SharedSessionContractImplementor session)
    	1- session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, callable ) -> M54_01
            ?- 构建 PreparedStatement
    	2- expectation.verifyOutcome(session.getJdbcCoordinator().getResultSetReturn().executeUpdate( insert ), insert, -1);
    M53_02-  insert(Serializable id,Object[] fields,boolean[] notNull,int j,String sql,Object object,SharedSessionContractImplementor session)
        1- session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, callable )
            ?- 此处获取  statement
        

// Step 2 : statement 执行语句
C54- StatementPreparerImpl
    M54_01- prepareStatement(String sql, final boolean isCallable) -> M54_02
        - PreparedStatement preparedStatement = doPrepare() : 构建 statement
        - 执行 postProcess -> M54_03
    M54_02- buildPreparedStatementPreparationTemplate -> M55_01
    M54_03- postProcess
    	- jdbcCoordinator.getResourceRegistry().register( preparedStatement, true )
    
// ConnectionImpl 执行数据库连接
C55- ConnectionImpl
    M55_01- prepareStatement(String sql, int resultSetType, int resultSetConcurrency) 
    	?- 其中包括 sql 类型 , 返回类型 - >M55_02
    M55_02- clientPrepareStatement
    	 - ClientPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database) -> PS:M55_02_01

// PS:M55_02_01 
com.mysql.cj.jdbc.ClientPreparedStatement: insert into user (isactive, orgid, remark, userlink, username, usertype, userid) values (** NOT SPECIFIED **, ** NOT SPECIFIED **, ** NOT SPECIFIED **, ** NOT SPECIFIED **, ** NOT SPECIFIED **, ** NOT SPECIFIED **, ** NOT SPECIFIED **)
    
C56- ResourceRegistryStandardImpl
    M56_01- register(Statement statement, boolean cancelable)
    	- Set<ResultSet> previousValue = xref.putIfAbsent( statement, Collections.EMPTY_SET );

// 最终处理类
C57- ResultSetReturnImpl
	M57_01- executeUpdate(PreparedStatement statement) : 执行 update 操作
            - jdbcCoordinator.getJdbcSessionOwner().getJdbcSessionContext().getObserver().jdbcExecuteStatementStart()
 

executeUpdate 处理流程

  • ResultSetReturnImpl # executeUpdate
  • HikariProxyPreparedStatement # executeUpdate
  • ProxyPreparedStatement # executeUpdate
  • ClientPreparedStatement # executeUpdate
  • ClientPreparedStatement # executeLargeUpdate
  • ClientPreparedStatement # executeUpdateInternal
  • ClientPreparedStatement # fillSendPacket
C58- SessionEventListenerManagerImpl
    M58_01- jdbcExecuteStatementStart()
    	- 发起 SessionEventListener 处理 listener.jdbcExecuteStatementStart()
    	- 调用 executeUpdate()
    
C59- ClientPreparedStatement
    M59_01- executeUpdate() --> 核心调用为 M59_02
    M59_02- executeUpdateInternal(QueryBindings<?> bindings, boolean isReallyBatch)
        -  Message sendPacket = ((PreparedQuery<?>) this.query).fillSendPacket(bindings); : 此时已经生成一个 Message 对象
    
// 最终的处理流程 , SQL 的封装
C60- AbstractPreparedQuery
    M60_01- fillSendPacket(QueryBindings<?> bindings)
    	?- bindValues 核心逻辑 ->

M60_01 源代码 : bindvalue 的绑定处理

public <M extends Message> M fillSendPacket(QueryBindings<?> bindings) {
        synchronized (this) {
            // 绑定参数
            BindValue[] bindValues = bindings.getBindValues();
            // 准备最终返回的对象
            NativePacketPayload sendPacket = this.session.getSharedSendPacket();

            sendPacket.writeInteger(IntegerDataType.INT1, NativeConstants.COM_QUERY);

            boolean useStreamLengths = this.useStreamLengthsInPrepStmts.getValue();

            int ensurePacketSize = 0;

            String statementComment = this.session.getProtocol().getQueryComment();

            byte[] commentAsBytes = null;

            if (statementComment != null) {
                commentAsBytes = StringUtils.getBytes(statementComment, this.charEncoding);

                ensurePacketSize += commentAsBytes.length;
                ensurePacketSize += 6; // for /*[space] [space]*/
            }
            
            // 统计PacketSize
            for (int i = 0; i < bindValues.length; i++) {
                if (bindValues[i].isStream() && useStreamLengths) {
                    ensurePacketSize += bindValues[i].getStreamLength();
                }
            }
            
            // 检查底层缓冲区是否有足够的空间从当前位置开始存储additionalData字节。
            // 如果缓冲区的大小小于所需的大小,那么将以更大的大小重新分配它
            if (ensurePacketSize != 0) {
                sendPacket.ensureCapacity(ensurePacketSize);
            }

            if (commentAsBytes != null) {
                // 固定长度的字符串有一个已知的硬编码长度
                sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SLASH_STAR_SPACE_AS_BYTES);
                sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, commentAsBytes);
                sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES);
            }
            
            // 源 SQL 语句 : insert into user (isactive, orgid, remark, userlink, username, usertype, userid) values (?, ?, ?, ?, ?, ?, ?)
            byte[][] staticSqlStrings = this.parseInfo.getStaticSql();
            // bindValues 是参数列表 -> PS:M60_01_01
            for (int i = 0; i < bindValues.length; i++) {
                bindings.checkParameterSet(i);
				
                sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, staticSqlStrings[i]);

                if (bindValues[i].isStream()) {
                    streamToBytes(sendPacket, bindValues[i].getStreamValue(), true, bindValues[i].getStreamLength(), useStreamLengths);
                } else {
                    sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, bindValues[i].getByteValue());
                }
            }
		   // 组合为 NativeSQL 字符数组
            // get one byte to string -> insert into user (isactive, orgid, remark, userlink, username, usertype, userid) values (0, '1', 'step1 ', null, 'gang', null, 0)                                                   
            sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, staticSqlStrings[bindValues.length]);

            return (M) sendPacket;
        }
}    


// 数组一 : 
for (int j = 0; j < staticSqlStrings.length; j++) {
    System.out.println("staticSqlStrings value [" + new String(staticSqlStrings[j]) + "]");
}

// 数组一结果>>>>>>>>>>>>>>>>>>: 
staticSqlStrings value [insert into user (isactive, orgid, remark, userlink, username, usertype, userid) values (]
staticSqlStrings value [, ]
staticSqlStrings value [, ]
staticSqlStrings value [, ]
staticSqlStrings value [, ]
staticSqlStrings value [, ]
staticSqlStrings value [, ]
staticSqlStrings value [)]

// 数组二  :
for (int j = 0; j < bindValues.length; j++) {
    System.out.println("name [" + i + "]-- value [" + new String(bindValues[j].getByteValue()) + "]");
}

// 数组二结果>>>>>>>>>>>>>>>>>>>
name [0]-- value [0]
name [1]-- value ['1']
name [2]-- value ['step1 ']
name [3]-- value [null]
name [4]-- value ['gang']
name [5]-- value [null]
name [6]-- value [0]
                                                                                                                 
// 总结 : 至此 , JPA 的SQL 解析就完成了 , 可以看到 , 是通过2个数组互相拼装完成后 , 下面我们分析一下其中的几个关键节点                                                   

PS:M60_01_01 : bindValue 对应参数

JPA_Param001.jpg

2.2 补充节点 : OriginSQL 的生成

我把 staticSqlStrings 称为 OriginSQL , 把 sendPacket 的 SQL 称为 NativeSQL

OriginSQL 的前世今生

上文从 this.parseInfo.getStaticSql() 中获取的 SQL 实际上已经被切割好了 , 我们从源头看看是什么时候放进去的 :

//  先看一下体系
C- ClientPreparedStatement
    E- StatementImpl
        F- protected Query query;
    MC- ClientPreparedStatement(JdbcConnection conn, String sql, String catalog, ParseInfo cachedParseInfo)
        - ((PreparedQuery<?>) this.query).checkNullOrEmptyQuery(sql);
        - ((PreparedQuery<?>) this.query).setOriginalSql(sql);
        - ((PreparedQuery<?>) this.query).setParseInfo(cachedParseInfo != null ? cachedParseInfo : new ParseInfo(sql, this.session, this.charEncoding));

// 可以看到 , 在 ClientPreparedStatement 的构造方法中 , ParseInfo 就生成了 , 所以核心还是在 ParseInfo 对象中


C63- ParseInfo
    MC63_01- ParseInfo(String sql, Session session, String encoding, boolean buildRewriteInfo)
        ?- 在该构造方法中 , 对 sql 进行了解析 

来吧 , 非要把这一段代码看一遍 ,按照 Step 过一遍就行

 public ParseInfo(String sql, Session session, String encoding, boolean buildRewriteInfo) {

        try {
            // Step 1 : 保证 sql 不为空
            if (sql == null) {
                throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedStatement.61"),
                        session.getExceptionInterceptor());
            }
            
            // Step 2 : 字符编码和时间
            this.charEncoding = encoding;
            this.lastUsed = System.currentTimeMillis();

            // 直译 : 获取标识符引用字符串 , 以下是一个完整体系 , 为了得到 quotedIdentifierChar
            String quotedIdentifierString = session.getIdentifierQuoteString();
            char quotedIdentifierChar = 0;
            if ((quotedIdentifierString != null) && !quotedIdentifierString.equals(" ") && (quotedIdentifierString.length() > 0)) {
                quotedIdentifierChar = quotedIdentifierString.charAt(0);
            }
            
            // sql 长度 , 部分框架会校验长度来保证安全性 , 像个 sign 试的 , 猜测可能也是这个原因
            this.statementLength = sql.length();

            ArrayList<int[]> endpointList = new ArrayList<>();
            boolean inQuotes = false;
            char quoteChar = 0;
            boolean inQuotedId = false;
            int lastParmEnd = 0;
            int i;

            // 没有反斜杠转义集
            boolean noBackslashEscapes = session.getServerSession().isNoBackslashEscapesSet();
            
            // 发现了一个有趣的地方 , 这里会去查找真正的起点 , 这也意味着实际上我的 SQL 可以有注释 , 而且不会解析 ???
            // PS : 想多了 ..... QuerySyntaxException 已经做了拦截
            // StringUtils.startsWithIgnoreCaseAndWs(sql, "/*")
            // statementStartPos = sql.indexOf("*/");
            // StringUtils.startsWithIgnoreCaseAndWs(sql, "--") || StringUtils.startsWithIgnoreCaseAndWs(sql, "#")
            this.statementStartPos = findStartOfStatement(sql);
            
            // Step 3 : 字符级逐字处理
            // - 扫描操作类型 (Insert / select 等)
            // - 扫描 '' "" 字符 ,跳过
            // - 扫描 ? 占位符 , 记录位置
            // - 扫描 ; 及 \n \r 换行符 , 直接跳出循环
            // - 
            for (i = this.statementStartPos; i < this.statementLength; ++i) {
                char c = sql.charAt(i);

                if ((this.firstStmtChar == 0) && Character.isLetter(c)) {
                    // 确定我们正在执行的语句类型 , 即是 insert / update / select 等
                    this.firstStmtChar = Character.toUpperCase(c);

                    // 如果不是INSERT语句,则不需要搜索“ON DUPLICATE KEY UPDATE”
                    if (this.firstStmtChar == 'I') {
                        this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(sql,
                                session.getPropertySet().getBooleanProperty(PropertyKey.dontCheckOnDuplicateKeyUpdateInSQL).getValue(),
                                session.getPropertySet().getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue(),
                                session.getServerSession().isNoBackslashEscapesSet());
                        // 相反 , 如果是 insert , 此处需要处理  DUPLICATE KEY
                        // 当insert已经存在的记录时,执行Update
                        this.isOnDuplicateKeyUpdate = this.locationOfOnDuplicateKeyUpdate != -1;
                    }
                }
                
                // 当 \\ 时 , 下一个字符为转义字符 , 即转义字符的处理 == 不处理 , 跳过
                if (!noBackslashEscapes && c == '\\' && i < (this.statementLength - 1)) {
                    i++;
                    continue; 
                }

                // 
                if (!inQuotes && (quotedIdentifierChar != 0) && (c == quotedIdentifierChar)) {
                    inQuotedId = !inQuotedId;
                } else if (!inQuotedId) {
                    //  只有在不使用引号标识符时才使用引号
                    if (inQuotes) {
                        // 处理 ' 和 " 字符 , 这里也意味着2种字符均可
                        // 这里可能存在问题 , if 和 else if 条件一样
                        if (((c == '\'') || (c == '"')) && c == quoteChar) {
                            if (i < (this.statementLength - 1) && sql.charAt(i + 1) == quoteChar) {
                                i++;
                                continue; // inline quote escape
                            }

                            inQuotes = !inQuotes;
                            quoteChar = 0;
                        } else if (((c == '\'') || (c == '"')) && c == quoteChar) {
                            inQuotes = !inQuotes;
                            quoteChar = 0;
                        }
                    } else {
                        // 对 # 号进行处理
                        if (c == '#' || (c == '-' && (i + 1) < this.statementLength && sql.charAt(i + 1) == '-')) {
                            // 运行到语句结束,或换行符,以先出现者为准
                            int endOfStmt = this.statementLength - 1;

                            for (; i < endOfStmt; i++) {
                                c = sql.charAt(i);
                             // 这里时换行符或者结尾
                                if (c == '\r' || c == '\n') {
                                    break;
                                }
                            }

                            continue;
                        } else if (c == '/' && (i + 1) < this.statementLength) {
                            // Comment?
                            char cNext = sql.charAt(i + 1);

                            if (cNext == '*') {
                                i += 2;

                                for (int j = i; j < this.statementLength; j++) {
                                    i++;
                                    cNext = sql.charAt(j);

                                    if (cNext == '*' && (j + 1) < this.statementLength) {
                                        if (sql.charAt(j + 1) == '/') {
                                            i++;

                                            if (i < this.statementLength) {
                                                c = sql.charAt(i);
                                            }

                                            break; // comment done
                                        }
                                    }
                                }
                            }
                        } else if ((c == '\'') || (c == '"')) {
                            inQuotes = true;
                            quoteChar = c;
                        }
                    }
                }

                if (!inQuotes && !inQuotedId) {
                    // 此处处理 ? 占位符
                    if ((c == '?')) {
                        // 记录当前占位符字符
                        endpointList.add(new int[] { lastParmEnd, i });
                        // 字符 + 1 , 处理后续字符
                        lastParmEnd = i + 1;
                        
                        // 是否在重复密钥更新
                      // 重复密钥更新的位置
                        if (this.isOnDuplicateKeyUpdate && i > this.locationOfOnDuplicateKeyUpdate) {
                            this.parametersInDuplicateKeyClause = true;
                        }
                    // 处理结尾符号
                    } else if (c == ';') {
                        int j = i + 1;
                        if (j < this.statementLength) {
                            for (; j < this.statementLength; j++) {
                                if (!Character.isWhitespace(sql.charAt(j))) {
                                    break;
                                }
                            }
                            if (j < this.statementLength) {
                                this.numberOfQueries++;
                            }
                            i = j - 1;
                        }
                    }
                }
            }
            
            // 自此 SQL 的扫描已经完成 , 后续进行 SQL 的切割
            
            if (this.firstStmtChar == 'L') {
                if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) {
                    this.foundLoadData = true;
                } else {
                    this.foundLoadData = false;
                }
            } else {
                this.foundLoadData = false;
            }

            endpointList.add(new int[] { lastParmEnd, this.statementLength });
            this.staticSql = new byte[endpointList.size()][];
            this.hasPlaceholders = this.staticSql.length > 1;

            // Step 5 : 此处按照扫描的节点进行字符串切割
            for (i = 0; i < this.staticSql.length; i++) {
                int[] ep = endpointList.get(i);
                int end = ep[1];
                int begin = ep[0];
                int len = end - begin;

                if (this.foundLoadData) {
                    this.staticSql[i] = StringUtils.getBytes(sql, begin, len);
                } else if (encoding == null) {
                    byte[] buf = new byte[len];

                    for (int j = 0; j < len; j++) {
                        buf[j] = (byte) sql.charAt(begin + j);
                    }

                    this.staticSql[i] = buf;
                } else {
                    // 切割字符串
                    this.staticSql[i] = StringUtils.getBytes(sql, begin, len, encoding);
                }
            }
            
            // PS : 切割的结果即为上文数组一的结果
        } catch (StringIndexOutOfBoundsException oobEx) {
            throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedStatement.62", new Object[] { sql }), oobEx,
                    session.getExceptionInterceptor());
        }

        if (buildRewriteInfo) {
            this.canRewriteAsMultiValueInsert = this.numberOfQueries == 1 && !this.parametersInDuplicateKeyClause
                    && canRewrite(sql, this.isOnDuplicateKeyUpdate, this.locationOfOnDuplicateKeyUpdate, this.statementStartPos);
            if (this.canRewriteAsMultiValueInsert && session.getPropertySet().getBooleanProperty(PropertyKey.rewriteBatchedStatements).getValue()) {
                buildRewriteBatchedParams(sql, session, encoding);
            }
        }

    }   


2.3 Entity 转换为 SQL

即实体类转换为
insert into user (isactive, orgid, remark, userlink, username, usertype, userid) values (?, ?, ?, ?, ?, ?, ?)


// 以及还有一个 , 最初 的 sql 是怎么通过 Entity 翻译过来的

C53- AbstractEntityPersister
    M53_01- insert(Serializable id, Object[] fields, Object object, SharedSessionContractImplementor session)
        P- fields : 实体类值数组
        P- object : 当前实体类   
    M53_03- doLateInit : 初始化 Persister -> 
    
    
    public void insert(Serializable id, Object[] fields, Object object, SharedSessionContractImplementor session) {
        // 生成应用内存中任何预插入值
        preInsertInMemoryValueGeneration( fields, object, session );

         // 获取 Table 数量   
        final int span = getTableSpan();
        if ( entityMetamodel.isDynamicInsert() ) {
            // F对于dynamic-insert="true"的情况,需要生成INSERT SQL
            boolean[] notNull = getPropertiesToInsert( fields );
            for ( int j = 0; j < span; j++ ) {
                insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
            }
        }
        else {
            // 对于dynamic-insert="false"的情况,使用静态SQL
            for ( int j = 0; j < span; j++ ) {
                 // getSQLInsertStrings()[j] 方法 , 这里直接从获取得属性 sqlInsertStrings -> PS:M53_01_01
                insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
            }
        }
    } 
    


PS:M53_01_01 AbstractEntityPersister 中的默认语句

JPA_AbstractEntityPersister_Module.jpg

从图片中就不难发现 , AbstractEntityPersister 中已经预先生成了相关的属性 , 直接获取

流程

// 这些属性值是在以下流程中生成得 : 
C53- AbstractEntityPersister
    M53_03- doLateInit : 初始化 Persister 
        
// 主要流程点 : 
C- LocalContainerEntityManagerFactoryBean # afterPropertiesSet
C- AbstractEntityManagerFactoryBean # buildNativeEntityManagerFactory
C- AbstractEntityManagerFactoryBean # createNativeEntityManagerFactory
C- SpringHibernateJpaPersistenceProvider # createContainerEntityManagerFactory
C- SessionFactoryBuilderImpl # build    
C- SessionFactoryImpl # init
C- MetamodelImpl # initialize

// 最终流程点 : 执行 AbstractEntityPersister 初始化    
C- AbstractEntityPersister # postInstantiate

实际生成方法 源代码

C53- AbstractEntityPersister
    M53_04- generateInsertString        
        
protected String generateInsertString(boolean identityInsert, boolean[] includeProperty, int j) {

        // 此处 Insert 对象包含方言和表明
        Insert insert = new Insert( getFactory().getDialect() )
                .setTableName( getTableName( j ) );

        // add normal properties
        for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
            // the incoming 'includeProperty' array only accounts for insertable defined at the root level, it
            // does not account for partially generated composites etc.  We also need to account for generation
            // values
            if ( isPropertyOfTable( i, j ) ) {
                if ( !lobProperties.contains( i ) ) {
                    final InDatabaseValueGenerationStrategy generationStrategy = entityMetamodel.getInDatabaseValueGenerationStrategies()[i];
                    if ( generationStrategy != null && generationStrategy.getGenerationTiming().includesInsert() ) {
                        if ( generationStrategy.referenceColumnsInSql() ) {
                            final String[] values;
                            if ( generationStrategy.getReferencedColumnValues() == null ) {
                                values = propertyColumnWriters[i];
                            }
                            else {
                                final int numberOfColumns = propertyColumnWriters[i].length;
                                values = new String[numberOfColumns];
                                for ( int x = 0; x < numberOfColumns; x++ ) {
                                    if ( generationStrategy.getReferencedColumnValues()[x] != null ) {
                                        values[x] = generationStrategy.getReferencedColumnValues()[x];
                                    }
                                    else {
                                        values[x] = propertyColumnWriters[i][x];
                                    }
                                }
                            }
                            insert.addColumns( getPropertyColumnNames( i ), propertyColumnInsertable[i], values );
                        }
                    }
                    else if ( includeProperty[i] ) {
                          // 插入 Columns
                        insert.addColumns(
                                getPropertyColumnNames( i ),
                                propertyColumnInsertable[i],
                                propertyColumnWriters[i]
                        );
                    }
                }
            }
        }

        // 添加鉴频器 , 简单点说就是为了让一个数据库对象映射为多种不同得 JavaBean 
        // Discriminator是一种基于单个数据库表中包含的数据的继承策略。
        @ https://www.waitingforcode.com/jpa/single-table-inheritance-with-discriminator-in-jpa/read#:~:text=Discriminator%20is%20an%20inheritance%20strategy%20based%20on%20data,this%20type%20of%20inheritance%2C%20JPA%20provides%20two%20annotations%3A
        
        
        if ( j == 0 ) {
            addDiscriminatorToInsert( insert );
        }

        // 如果需要插入主键 , 则添加
        if ( j == 0 && identityInsert ) {
            insert.addIdentityColumn( getKeyColumns( 0 )[0] );
        }
        else {
            insert.addColumns( getKeyColumns( j ) );
        }
        
        // 插入前缀
        if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
            insert.setComment( "insert " + getEntityName() );
        }

        for ( int i : lobProperties ) {
            if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
                // 此属性属于表,将被插入
                insert.addColumns(
                        getPropertyColumnNames( i ),
                        propertyColumnInsertable[i],
                        propertyColumnWriters[i]
                );
            }
        }

        String result = insert.toStatementString();

        // append the SQL to return the generated identifier
        if ( j == 0 && identityInsert && useInsertSelectIdentity() ) { //TODO: suck into Insert
            result = getFactory().getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( result );
        }

        return result;
    }
 

总结

整体有三个节点 :
解析的实际节点 : ClientPreparedStatement # fillSendPacket
SQL 的处理 : ParseInfo
Entity 翻译为 SQL : AbstractEntityPersister # generateInsertString