Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.querydsl.jpa;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.ParamExpression;
import com.querydsl.core.types.ParamNotSetException;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.Param;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;

/**
* Helpers shared by {@link com.querydsl.jpa.impl.JPAInsertClause} and {@link
* com.querydsl.jpa.hibernate.HibernateInsertClause} to support {@code executeWithKey()} via native
* SQL INSERT.
*
* <p>This is an internal API and not intended for direct use by application code.
*/
public final class JpaInsertNativeHelper {

private JpaInsertNativeHelper() {}

/**
* Resolve the effective column paths from either the {@code set()}-style inserts map or the
* {@code columns()}-style list. The {@code set()}-style takes precedence when present.
*/
public static List<Path<?>> effectiveColumns(
Map<Path<?>, Expression<?>> inserts, List<Path<?>> columns) {
if (!inserts.isEmpty()) {
return new ArrayList<>(inserts.keySet());
}
return new ArrayList<>(columns);
}

/**
* Resolve the effective value expressions, in the order matching {@link #effectiveColumns(Map,
* List)}. Raw values from the {@code values()}-style call are wrapped as constants; expressions
* are passed through unchanged.
*/
public static List<Expression<?>> effectiveValues(
Map<Path<?>, Expression<?>> inserts, List<Object> values) {
if (!inserts.isEmpty()) {
return new ArrayList<>(inserts.values());
}
var result = new ArrayList<Expression<?>>(values.size());
for (Object v : values) {
if (v instanceof Expression<?> expression) {
result.add(expression);
} else {
result.add(Expressions.constant(v));
}
}
return result;
}

/**
* Resolve constant values from the serializer, unwrapping {@link Param} expressions against the
* provided parameter bindings.
*
* @param constants the constants accumulated by the serializer
* @param params the parameter bindings collected from {@link
* com.querydsl.core.QueryMetadata#getParams()}
* @return resolved values ready for JDBC binding
*/
public static Object[] resolveConstants(
List<Object> constants, Map<ParamExpression<?>, Object> params) {
var result = new Object[constants.size()];
for (var i = 0; i < constants.size(); i++) {
var val = constants.get(i);
if (val instanceof Param<?> param) {
val = params.get(val);
if (val == null) {
throw new ParamNotSetException(param);
}
}
result[i] = val;
}
return result;
}

/**
* Execute a native SQL INSERT with {@code RETURN_GENERATED_KEYS} and return the generated key.
*
* @param <T> the key type
* @param conn the JDBC connection (not closed by this method)
* @param sql the native SQL INSERT string
* @param params the parameter values to bind, in positional order
* @param keyType the expected key type
* @return the generated key, or {@code null} if no rows were inserted
* @throws SQLException if a database error occurs
*/
@Nullable
public static <T> T executeAndReturnKey(
java.sql.Connection conn, String sql, Object[] params, Class<T> keyType) throws SQLException {
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}
stmt.executeUpdate();

try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
return rs.getObject(1, keyType);
}
return null;
}
}
}

/**
* Execute a native SQL multi-row INSERT with {@code RETURN_GENERATED_KEYS} and return all
* generated keys.
*
* @param <T> the key type
* @param conn the JDBC connection (not closed by this method)
* @param sql the native SQL INSERT string (typically multi-row {@code VALUES (..),(..)})
* @param params the parameter values to bind, in positional order
* @param keyType the expected key type
* @return the generated keys in row order; empty list if no rows were inserted
* @throws SQLException if a database error occurs
*/
public static <T> List<T> executeAndReturnKeys(
java.sql.Connection conn, String sql, Object[] params, Class<T> keyType) throws SQLException {
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}
stmt.executeUpdate();

var keys = new ArrayList<T>();
try (ResultSet rs = stmt.getGeneratedKeys()) {
while (rs.next()) {
keys.add(rs.getObject(1, keyType));
}
}
return keys;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.querydsl.jpa;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Path;
import com.querydsl.sql.Configuration;
import com.querydsl.sql.SQLSerializer;
import com.querydsl.sql.SQLTemplates;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import java.util.List;

/**
* Serializer that emits a native SQL {@code INSERT} statement from JPA entity metadata
* ({@code @Table}/{@code @Column} annotations) and a list of column/value expressions.
*
* <p>Unlike {@link NativeSQLSerializer}, which targets Hibernate native queries with positional
* {@code ?N} placeholders, this serializer emits plain {@code ?} placeholders for direct binding to
* a JDBC {@link java.sql.PreparedStatement}, and dispatches each value expression through the
* visitor pattern so function templates, paths, parameters and other non-trivial expressions
* serialize into SQL correctly.
*
* <p>This is an internal API used by {@link com.querydsl.jpa.impl.JPAInsertClause} and {@link
* com.querydsl.jpa.hibernate.HibernateInsertClause} to support {@code executeWithKey()}.
*/
public final class JpaNativeInsertSerializer extends SQLSerializer {

public JpaNativeInsertSerializer(Configuration configuration) {
super(configuration);
}

@Override
protected void appendAsColumnName(Path<?> path, boolean precededByDot) {
if (path.getAnnotatedElement() != null
&& path.getAnnotatedElement().isAnnotationPresent(Column.class)) {
var column = path.getAnnotatedElement().getAnnotation(Column.class);
if (!column.name().isEmpty()) {
append(getTemplates().quoteIdentifier(column.name(), precededByDot));
return;
}
}
super.appendAsColumnName(path, precededByDot);
}

/**
* Serialize a single-row {@code INSERT} statement for the given entity, columns, and value
* expressions. Each value expression is dispatched through the visitor pattern, so function
* templates, paths, parameters, and other expressions are serialized into SQL with positional
* {@code ?} placeholders. Bound values are accumulated and accessible via {@link
* #getConstants()}.
*
* @param entityClass the JPA entity class (used to resolve the table name via {@link Table})
* @param columns the column paths to insert into
* @param values the value expressions, one per column, in matching order
*/
public void serializeInsert(
Class<?> entityClass, List<Path<?>> columns, List<Expression<?>> values) {
serializeInsertRows(entityClass, columns, List.of(values));
}

/**
* Serialize a multi-row {@code INSERT} statement for the given entity, columns, and rows of value
* expressions. Emits {@code INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...}.
*
* @param entityClass the JPA entity class (used to resolve the table name via {@link Table})
* @param columns the column paths to insert into
* @param rows the rows of value expressions; each row's size must match the column count
*/
public void serializeInsertRows(
Class<?> entityClass, List<Path<?>> columns, List<List<Expression<?>>> rows) {
if (rows.isEmpty()) {
throw new IllegalArgumentException("No rows specified for insert");
}
for (var row : rows) {
if (columns.size() != row.size()) {
throw new IllegalArgumentException(
"Column count ("
+ columns.size()
+ ") does not match value count ("
+ row.size()
+ ")");
}
}

var templates = getTemplates();
append(templates.getInsertInto());
appendTable(entityClass);

if (!columns.isEmpty()) {
append(" (");
var first = true;
for (Path<?> col : columns) {
if (!first) {
append(", ");
}
appendAsColumnName(col, false);
first = false;
}
append(")");
}

var oldSkipParent = skipParent;
skipParent = true;
try {
append(templates.getValues());
var firstRow = true;
for (var row : rows) {
if (!firstRow) {
append(", ");
}
append("(");
var firstValue = true;
for (Expression<?> value : row) {
if (!firstValue) {
append(", ");
}
handle(value);
firstValue = false;
}
append(")");
firstRow = false;
}
} finally {
skipParent = oldSkipParent;
}
}

private void appendTable(Class<?> entityClass) {
SQLTemplates templates = getTemplates();
String schema = "";
String tableName = entityClass.getSimpleName();
if (entityClass.isAnnotationPresent(Table.class)) {
var table = entityClass.getAnnotation(Table.class);
if (!table.name().isEmpty()) {
tableName = table.name();
}
schema = table.schema();
}
if (!schema.isEmpty()) {
append(templates.quoteIdentifier(schema));
append(".");
append(templates.quoteIdentifier(tableName, true));
} else {
append(templates.quoteIdentifier(tableName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package com.querydsl.jpa.hibernate;

import org.hibernate.Session;
import org.hibernate.jdbc.ReturningWork;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;

Expand All @@ -39,4 +40,9 @@ public Query<?> createQuery(String queryString) {
public NativeQuery<?> createSQLQuery(String queryString) {
return session.createNativeQuery(queryString);
}

@Override
public <T> T doReturningWork(ReturningWork<T> work) {
return session.doReturningWork(work);
}
}
Loading
Loading