This commit is contained in:
commit
af03293df7
|
@ -0,0 +1,52 @@
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
build
|
||||||
|
.gradle
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# out
|
||||||
|
**/out/
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
*.pid
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
# Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
*.tmp
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.guwan.backend</groupId>
|
||||||
|
<artifactId>jpa-test</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>jpa-test</name>
|
||||||
|
<description>Custom JPA Join Query Demo</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.guwan.backend;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableJpaRepositories
|
||||||
|
public class JpaTestApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(JpaTestApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,570 @@
|
||||||
|
package com.guwan.backend.MergeStrategy;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.criteria.*;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.data.jpa.repository.query.QueryUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义连表查询构建器
|
||||||
|
* 用于构建复杂的多表连接查询,支持指定返回字段和条件
|
||||||
|
*/
|
||||||
|
public class JoinBuilder<T> {
|
||||||
|
private final Class<T> rootClass;
|
||||||
|
private final Map<Class<?>, JoinInfo> joins = new HashMap<>();
|
||||||
|
private final Map<Class<?>, List<Condition>> conditions = new HashMap<>();
|
||||||
|
private Map<Class<?>, String[]> selectedFields;
|
||||||
|
private Map<String, Object> namedSelections = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private JoinBuilder(Class<T> rootClass) {
|
||||||
|
this.rootClass = rootClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> JoinBuilder<T> from(Class<T> rootClass) {
|
||||||
|
return new JoinBuilder<>(rootClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加连接表
|
||||||
|
* @param joinClass 要连接的表实体类
|
||||||
|
* @param sourceField 主表的外键字段
|
||||||
|
* @param targetField 连接表的目标字段
|
||||||
|
*/
|
||||||
|
public JoinBuilder<T> join(Class<?> joinClass, String sourceField, String targetField) {
|
||||||
|
joins.put(joinClass, new JoinInfo(sourceField, targetField));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加连接表(使用方法引用)
|
||||||
|
* @param joinClass 要连接的表实体类
|
||||||
|
* @param sourceFieldGetter 主表外键字段的getter方法引用
|
||||||
|
* @param targetFieldGetter 连接表目标字段的getter方法引用
|
||||||
|
*/
|
||||||
|
public <J, V> JoinBuilder<T> join(Class<J> joinClass,
|
||||||
|
Function<T, V> sourceFieldGetter,
|
||||||
|
Function<J, V> targetFieldGetter) {
|
||||||
|
String sourceField = getFieldNameFromGetter(sourceFieldGetter);
|
||||||
|
String targetField = getFieldNameFromGetter(targetFieldGetter);
|
||||||
|
return join(joinClass, sourceField, targetField);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加等值条件
|
||||||
|
* @param entityClass 实体类
|
||||||
|
* @param fieldName 字段名
|
||||||
|
* @param value 字段值
|
||||||
|
*/
|
||||||
|
public <V> JoinBuilder<T> equal(Class<?> entityClass, String fieldName, V value) {
|
||||||
|
if (!conditions.containsKey(entityClass)) {
|
||||||
|
conditions.put(entityClass, new ArrayList<>());
|
||||||
|
}
|
||||||
|
conditions.get(entityClass).add(new Condition(fieldName, value, Operator.EQUAL));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加等值条件(使用方法引用)
|
||||||
|
* @param entityClass 实体类
|
||||||
|
* @param fieldGetter 字段的getter方法引用
|
||||||
|
* @param value 字段值
|
||||||
|
*/
|
||||||
|
public <E, V> JoinBuilder<T> equal(Class<E> entityClass, Function<E, V> fieldGetter, V value) {
|
||||||
|
String fieldName = getFieldNameFromGetter(fieldGetter);
|
||||||
|
return equal(entityClass, fieldName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择返回的字段
|
||||||
|
* @param selectedFields 每个实体类需要返回的字段映射
|
||||||
|
*/
|
||||||
|
public JoinBuilder<T> selectFields(Map<Class<?>, String[]> selectedFields) {
|
||||||
|
this.selectedFields = selectedFields;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择返回的字段(使用方法引用)
|
||||||
|
* @param fieldGetters 方法引用列表
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public final <E> JoinBuilder<T> select(Class<E> entityClass, Function<E, ?>... fieldGetters) {
|
||||||
|
if (selectedFields == null) {
|
||||||
|
selectedFields = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] fieldNames = Arrays.stream(fieldGetters)
|
||||||
|
.map(this::getFieldNameFromGetter)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
|
||||||
|
selectedFields.put(entityClass, fieldNames);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个具有别名的字段选择(优先级高于select方法)
|
||||||
|
* @param alias 字段别名
|
||||||
|
* @param entityClass 实体类
|
||||||
|
* @param fieldGetter 字段的getter方法引用
|
||||||
|
*/
|
||||||
|
public <E, V> JoinBuilder<T> selectAs(String alias, Class<E> entityClass, Function<E, V> fieldGetter) {
|
||||||
|
String fieldName = getFieldNameFromGetter(fieldGetter);
|
||||||
|
|
||||||
|
// 存储字段信息,稍后在configureSelections处理
|
||||||
|
if (selectedFields == null) {
|
||||||
|
selectedFields = new HashMap<>();
|
||||||
|
}
|
||||||
|
if (!selectedFields.containsKey(entityClass)) {
|
||||||
|
selectedFields.put(entityClass, new String[0]);
|
||||||
|
}
|
||||||
|
namedSelections.put(alias, new SelectionInfo(entityClass, fieldName));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个具有别名的字段选择(字符串版本)
|
||||||
|
* @param alias 字段别名
|
||||||
|
* @param entityClass 实体类
|
||||||
|
* @param fieldName 字段名称
|
||||||
|
*/
|
||||||
|
public <E> JoinBuilder<T> selectAs(String alias, Class<E> entityClass, String fieldName) {
|
||||||
|
// 存储字段信息,稍后在configureSelections处理
|
||||||
|
if (selectedFields == null) {
|
||||||
|
selectedFields = new HashMap<>();
|
||||||
|
}
|
||||||
|
if (!selectedFields.containsKey(entityClass)) {
|
||||||
|
selectedFields.put(entityClass, new String[0]);
|
||||||
|
}
|
||||||
|
namedSelections.put(alias, new SelectionInfo(entityClass, fieldName));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置实体间字段映射关系的别名,以便对应DTO字段
|
||||||
|
* @param dtoClass DTO类
|
||||||
|
* @param mappings 字段映射
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public final <R> JoinBuilder<T> selectDto(Class<R> dtoClass, FieldMapping<R, ?>... mappings) {
|
||||||
|
for (FieldMapping<R, ?> mapping : mappings) {
|
||||||
|
String dtoFieldName = getFieldNameFromGetter(mapping.getDtoFieldGetter());
|
||||||
|
String entityFieldName = getFieldNameFromGetter(mapping.getEntityFieldGetter());
|
||||||
|
Class<?> entityClass = mapping.getEntityClass();
|
||||||
|
|
||||||
|
// 存储字段信息,稍后在configureSelections处理
|
||||||
|
if (selectedFields == null) {
|
||||||
|
selectedFields = new HashMap<>();
|
||||||
|
}
|
||||||
|
if (!selectedFields.containsKey(entityClass)) {
|
||||||
|
selectedFields.put(entityClass, new String[0]);
|
||||||
|
}
|
||||||
|
namedSelections.put(dtoFieldName, new SelectionInfo(entityClass, entityFieldName));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建规范查询
|
||||||
|
* @return Specification<T> 查询规范
|
||||||
|
*/
|
||||||
|
public Specification<T> build() {
|
||||||
|
return (root, query, criteriaBuilder) -> {
|
||||||
|
// 创建连接关系
|
||||||
|
Map<Class<?>, From<?, ?>> joinMap = createJoins(root, query);
|
||||||
|
|
||||||
|
// 应用连表条件和其他条件
|
||||||
|
List<Predicate> predicates = applyAllConditions(root, joinMap, criteriaBuilder);
|
||||||
|
|
||||||
|
// 设置查询结果投影
|
||||||
|
configureSelections(root, joinMap, query);
|
||||||
|
|
||||||
|
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行查询并返回结果
|
||||||
|
* @param entityManager EntityManager实例
|
||||||
|
* @return 查询结果列表
|
||||||
|
*/
|
||||||
|
public List<Object[]> execute(EntityManager entityManager) {
|
||||||
|
// 创建CriteriaBuilder
|
||||||
|
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
|
||||||
|
|
||||||
|
// 创建CriteriaQuery
|
||||||
|
CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);
|
||||||
|
|
||||||
|
// 设置根实体
|
||||||
|
Root<T> root = criteriaQuery.from(rootClass);
|
||||||
|
|
||||||
|
// 创建连接关系
|
||||||
|
Map<Class<?>, From<?, ?>> fromMap = createJoins(root, criteriaQuery);
|
||||||
|
|
||||||
|
// 应用所有条件,包括表之间的连接条件和额外查询条件
|
||||||
|
List<Predicate> predicates = applyAllConditions(root, fromMap, criteriaBuilder);
|
||||||
|
|
||||||
|
// 添加条件到查询
|
||||||
|
if (!predicates.isEmpty()) {
|
||||||
|
criteriaQuery.where(criteriaBuilder.and(predicates.toArray(new Predicate[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置查询结果投影
|
||||||
|
configureSelections(root, fromMap, criteriaQuery);
|
||||||
|
|
||||||
|
// 执行查询并返回结果
|
||||||
|
return entityManager.createQuery(criteriaQuery).getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建连接 - 修改为使用from而不是join
|
||||||
|
*/
|
||||||
|
private Map<Class<?>, From<?, ?>> createJoins(Root<T> root, CriteriaQuery<?> query) {
|
||||||
|
Map<Class<?>, From<?, ?>> fromMap = new HashMap<>();
|
||||||
|
fromMap.put(rootClass, root);
|
||||||
|
|
||||||
|
// 对于每个需要连接的表,创建一个单独的From
|
||||||
|
for (Map.Entry<Class<?>, JoinInfo> entry : joins.entrySet()) {
|
||||||
|
Class<?> joinClass = entry.getKey();
|
||||||
|
From<?, ?> joinRoot = query.from(joinClass);
|
||||||
|
fromMap.put(joinClass, joinRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用所有条件,包括表之间的连接条件和额外查询条件
|
||||||
|
*/
|
||||||
|
private List<Predicate> applyAllConditions(Root<T> root, Map<Class<?>, From<?, ?>> fromMap, CriteriaBuilder criteriaBuilder) {
|
||||||
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
// 首先添加表连接条件
|
||||||
|
for (Map.Entry<Class<?>, JoinInfo> entry : joins.entrySet()) {
|
||||||
|
Class<?> joinClass = entry.getKey();
|
||||||
|
JoinInfo joinInfo = entry.getValue();
|
||||||
|
|
||||||
|
if (fromMap.containsKey(joinClass)) {
|
||||||
|
From<?, ?> joinFrom = fromMap.get(joinClass);
|
||||||
|
Predicate joinCondition = criteriaBuilder.equal(
|
||||||
|
root.get(joinInfo.sourceField),
|
||||||
|
joinFrom.get(joinInfo.targetField)
|
||||||
|
);
|
||||||
|
predicates.add(joinCondition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后添加普通查询条件
|
||||||
|
for (Class<?> entityClass : conditions.keySet()) {
|
||||||
|
for (Condition condition : conditions.get(entityClass)) {
|
||||||
|
if (fromMap.containsKey(entityClass)) {
|
||||||
|
From<?, ?> from = fromMap.get(entityClass);
|
||||||
|
predicates.add(applyCondition(condition, from, criteriaBuilder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return predicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行查询并直接映射到DTO对象
|
||||||
|
* @param entityManager EntityManager实例
|
||||||
|
* @param dtoClass DTO类
|
||||||
|
* @param <R> DTO类型
|
||||||
|
* @return DTO对象列表
|
||||||
|
*/
|
||||||
|
public <R> List<R> executeAndMap(EntityManager entityManager, Class<R> dtoClass) {
|
||||||
|
List<Object[]> results = execute(entityManager);
|
||||||
|
return mapToDto(results, dtoClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将查询结果映射到DTO对象
|
||||||
|
* @param results 查询结果
|
||||||
|
* @param dtoClass DTO类
|
||||||
|
* @param <R> DTO类型
|
||||||
|
* @return DTO对象列表
|
||||||
|
*/
|
||||||
|
public <R> List<R> mapToDto(List<Object[]> results, Class<R> dtoClass) {
|
||||||
|
try {
|
||||||
|
// 获取所有字段名(顺序与查询结果一致)
|
||||||
|
List<String> fieldNames = new ArrayList<>(namedSelections.keySet());
|
||||||
|
|
||||||
|
// 获取构造函数
|
||||||
|
Class<?>[] paramTypes = new Class[fieldNames.size()];
|
||||||
|
Arrays.fill(paramTypes, Object.class);
|
||||||
|
Constructor<R> constructor = dtoClass.getDeclaredConstructor(paramTypes);
|
||||||
|
|
||||||
|
// 映射结果
|
||||||
|
return results.stream()
|
||||||
|
.map(row -> {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance(row);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to map result to DTO: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create DTO mapper: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置查询结果投影
|
||||||
|
*/
|
||||||
|
private void configureSelections(Root<T> root, Map<Class<?>, From<?, ?>> fromMap, CriteriaQuery<?> query) {
|
||||||
|
if (!namedSelections.isEmpty()) {
|
||||||
|
// 处理命名选择
|
||||||
|
List<Selection<?>> selections = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, Object> entry : namedSelections.entrySet()) {
|
||||||
|
String alias = entry.getKey();
|
||||||
|
Object value = entry.getValue();
|
||||||
|
|
||||||
|
if (value instanceof SelectionInfo) {
|
||||||
|
SelectionInfo info = (SelectionInfo) value;
|
||||||
|
Path<?> path;
|
||||||
|
|
||||||
|
if (fromMap.containsKey(info.entityClass)) {
|
||||||
|
path = fromMap.get(info.entityClass).get(info.fieldName);
|
||||||
|
selections.add(path.alias(alias));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selections.isEmpty()) {
|
||||||
|
query.multiselect(selections);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有命名选择或处理失败,回退到原来的处理方式
|
||||||
|
if (selectedFields != null && !selectedFields.isEmpty()) {
|
||||||
|
List<Selection<?>> selections = new ArrayList<>();
|
||||||
|
|
||||||
|
// 添加根实体的选择字段
|
||||||
|
if (selectedFields.containsKey(rootClass)) {
|
||||||
|
for (String field : selectedFields.get(rootClass)) {
|
||||||
|
selections.add(root.get(field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加关联实体的选择字段
|
||||||
|
for (Class<?> entityClass : selectedFields.keySet()) {
|
||||||
|
if (entityClass != rootClass && fromMap.containsKey(entityClass)) {
|
||||||
|
From<?, ?> from = fromMap.get(entityClass);
|
||||||
|
for (String field : selectedFields.get(entityClass)) {
|
||||||
|
selections.add(from.get(field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selections.isEmpty()) {
|
||||||
|
query.multiselect(selections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <X> Predicate applyCondition(Condition condition, From<?, X> from, CriteriaBuilder criteriaBuilder) {
|
||||||
|
switch (condition.operator) {
|
||||||
|
case EQUAL:
|
||||||
|
return criteriaBuilder.equal(from.get(condition.fieldName), condition.value);
|
||||||
|
// 可以添加更多操作符支持,如LIKE, IN, GREATER_THAN等
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Unsupported operator: " + condition.operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从getter方法引用中提取属性名
|
||||||
|
*/
|
||||||
|
private <E, R> String getFieldNameFromGetter(Function<E, R> getter) {
|
||||||
|
// 直接尝试从方法引用的toString中获取
|
||||||
|
try {
|
||||||
|
String methodRef = getter.toString();
|
||||||
|
if (methodRef.contains("::")) {
|
||||||
|
// 处理方法引用格式: com.example.Entity::getName
|
||||||
|
String methodName = methodRef.substring(methodRef.lastIndexOf("::") + 2);
|
||||||
|
return extractFieldNameFromMethod(methodName);
|
||||||
|
} else if (methodRef.contains("->")) {
|
||||||
|
// 处理Lambda表达式格式: (Entity e) -> e.getName()
|
||||||
|
String methodCall = methodRef.substring(methodRef.indexOf("->") + 2).trim();
|
||||||
|
String methodName = methodCall.substring(methodCall.lastIndexOf(".") + 1);
|
||||||
|
if (methodName.endsWith(")")) {
|
||||||
|
methodName = methodName.substring(0, methodName.indexOf("("));
|
||||||
|
}
|
||||||
|
return extractFieldNameFromMethod(methodName);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 忽略异常,尝试下一种方法
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String getterName = getter.getClass().getName();
|
||||||
|
if (getterName.contains("$$Lambda$")) {
|
||||||
|
// 处理Lambda表达式
|
||||||
|
if (getterName.contains("get")) {
|
||||||
|
// 尝试从名称中提取字段名
|
||||||
|
int getIndex = getterName.lastIndexOf("get");
|
||||||
|
if (getIndex >= 0 && getIndex + 3 < getterName.length()) {
|
||||||
|
String fieldName = getterName.substring(getIndex + 3);
|
||||||
|
if (fieldName.indexOf('$') > 0) {
|
||||||
|
fieldName = fieldName.substring(0, fieldName.indexOf('$'));
|
||||||
|
}
|
||||||
|
return fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 忽略异常,fallback到基于字符串的方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback到基于字符串的方法
|
||||||
|
String methodReference = getter.toString();
|
||||||
|
return getFieldNameFromGetterString(methodReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从getter方法字符串中提取属性名
|
||||||
|
*/
|
||||||
|
private String getFieldNameFromGetterString(String methodReference) {
|
||||||
|
// 方法引用的格式通常是:类名::get字段名 或 Lambda表达式
|
||||||
|
if (methodReference.contains("::")) {
|
||||||
|
// 处理方法引用格式: com.example.Entity::getName
|
||||||
|
String methodName = methodReference.substring(methodReference.lastIndexOf("::") + 2);
|
||||||
|
return extractFieldNameFromMethod(methodName);
|
||||||
|
} else if (methodReference.contains("->")) {
|
||||||
|
// 处理Lambda表达式格式: (Entity e) -> e.getName()
|
||||||
|
String methodCall = methodReference.substring(methodReference.indexOf("->") + 2).trim();
|
||||||
|
String methodName = methodCall.substring(methodCall.lastIndexOf(".") + 1);
|
||||||
|
if (methodName.endsWith(")")) {
|
||||||
|
methodName = methodName.substring(0, methodName.indexOf("("));
|
||||||
|
}
|
||||||
|
return extractFieldNameFromMethod(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无法识别的格式,返回原始字符串
|
||||||
|
return methodReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从方法名中提取字段名
|
||||||
|
*/
|
||||||
|
private String extractFieldNameFromMethod(String methodName) {
|
||||||
|
if (methodName.startsWith("get") && methodName.length() > 3) {
|
||||||
|
// getName -> name (首字母小写)
|
||||||
|
String fieldName = methodName.substring(3);
|
||||||
|
return fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
|
||||||
|
} else if (methodName.startsWith("is") && methodName.length() > 2) {
|
||||||
|
// isActive -> active (首字母小写)
|
||||||
|
String fieldName = methodName.substring(2);
|
||||||
|
return fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
|
||||||
|
}
|
||||||
|
return methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首字母大写
|
||||||
|
*/
|
||||||
|
private String capitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Operator {
|
||||||
|
EQUAL
|
||||||
|
// 可以添加更多操作符支持,如LIKE, IN, GREATER_THAN等
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Condition {
|
||||||
|
private final String fieldName;
|
||||||
|
private final Object value;
|
||||||
|
private final Operator operator;
|
||||||
|
|
||||||
|
Condition(String fieldName, Object value, Operator operator) {
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this.value = value;
|
||||||
|
this.operator = operator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接信息
|
||||||
|
*/
|
||||||
|
private static class JoinInfo {
|
||||||
|
private final String sourceField;
|
||||||
|
private final String targetField;
|
||||||
|
|
||||||
|
JoinInfo(String sourceField, String targetField) {
|
||||||
|
this.sourceField = sourceField;
|
||||||
|
this.targetField = targetField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择字段信息
|
||||||
|
*/
|
||||||
|
private static class SelectionInfo {
|
||||||
|
private final Class<?> entityClass;
|
||||||
|
private final String fieldName;
|
||||||
|
|
||||||
|
SelectionInfo(Class<?> entityClass, String fieldName) {
|
||||||
|
this.entityClass = entityClass;
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段映射定义,用于DTO映射
|
||||||
|
*/
|
||||||
|
public static class FieldMapping<D, E> {
|
||||||
|
private final Function<D, ?> dtoFieldGetter;
|
||||||
|
private final Function<E, ?> entityFieldGetter;
|
||||||
|
private final Class<E> entityClass;
|
||||||
|
|
||||||
|
private FieldMapping(Function<D, ?> dtoFieldGetter, Function<E, ?> entityFieldGetter, Class<E> entityClass) {
|
||||||
|
this.dtoFieldGetter = dtoFieldGetter;
|
||||||
|
this.entityFieldGetter = entityFieldGetter;
|
||||||
|
this.entityClass = entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <D, E, V> FieldMapping<D, E> of(Function<D, V> dtoFieldGetter, Function<E, V> entityFieldGetter) {
|
||||||
|
// 通过反射或其他方式获取entityClass
|
||||||
|
Class<E> entityClass = null;
|
||||||
|
try {
|
||||||
|
// 尝试从方法引用中提取类信息
|
||||||
|
String methodRef = entityFieldGetter.toString();
|
||||||
|
if (methodRef.contains("::")) {
|
||||||
|
String className = methodRef.substring(0, methodRef.indexOf("::"));
|
||||||
|
entityClass = (Class<E>) Class.forName(className);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略异常,entityClass将在运行时确定
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FieldMapping<>(dtoFieldGetter, entityFieldGetter, entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<D, ?> getDtoFieldGetter() {
|
||||||
|
return dtoFieldGetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<E, ?> getEntityFieldGetter() {
|
||||||
|
return entityFieldGetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> getEntityClass() {
|
||||||
|
return entityClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "departments")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Department {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "employees")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Employee {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "department_id")
|
||||||
|
private Long departmentId;
|
||||||
|
|
||||||
|
@Column(name = "project_id")
|
||||||
|
private Long projectId;
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/employees")
|
||||||
|
public class EmployeeController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmployeeService employeeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager entityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取员工详细信息,包含部门和项目信息
|
||||||
|
* 使用Spring JdbcTemplate直接映射SQL结果
|
||||||
|
*
|
||||||
|
* @param departmentName 部门名称过滤条件
|
||||||
|
* @param projectDeleted 项目是否已删除的过滤条件
|
||||||
|
* @return 员工DTO列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/details")
|
||||||
|
public List<EmployeeDTO> getEmployeeDetails(
|
||||||
|
@RequestParam(required = false) String departmentName,
|
||||||
|
@RequestParam(required = false, defaultValue = "false") boolean projectDeleted) {
|
||||||
|
return employeeService.getEmployeeDetails(departmentName, projectDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据条件查询员工信息
|
||||||
|
*
|
||||||
|
* @param employeeName 员工名称过滤条件,可选
|
||||||
|
* @param departmentName 部门名称过滤条件,可选
|
||||||
|
* @return 员工DTO列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/search")
|
||||||
|
public List<EmployeeDTO> searchEmployees(
|
||||||
|
@RequestParam(required = false) String employeeName,
|
||||||
|
@RequestParam(required = false) String departmentName) {
|
||||||
|
return employeeService.findEmployees(employeeName, departmentName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class EmployeeDTO {
|
||||||
|
private Long employeeId;
|
||||||
|
private String employeeName;
|
||||||
|
private String departmentName;
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于JoinBuilder映射的构造函数
|
||||||
|
* @param employeeId 员工ID
|
||||||
|
* @param employeeName 员工姓名
|
||||||
|
* @param departmentName 部门名称
|
||||||
|
* @param projectName 项目名称
|
||||||
|
*/
|
||||||
|
public EmployeeDTO(Object employeeId, Object employeeName, Object departmentName, Object projectName) {
|
||||||
|
this.employeeId = employeeId != null ? Long.valueOf(employeeId.toString()) : null;
|
||||||
|
this.employeeName = employeeName != null ? employeeName.toString() : null;
|
||||||
|
this.departmentName = departmentName != null ? departmentName.toString() : null;
|
||||||
|
this.projectName = projectName != null ? projectName.toString() : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface EmployeeRepository extends JpaRepository<Employee, Long>, JpaSpecificationExecutor<Employee> {
|
||||||
|
// 自定义查询方法可以在这里添加
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import com.guwan.backend.MergeStrategy.JoinBuilder;
|
||||||
|
import com.guwan.backend.MergeStrategy.JoinBuilder.FieldMapping;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmployeeService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmployeeRepository employeeRepository;
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
private EntityManager entityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取员工详细信息,包含部门和项目信息
|
||||||
|
* 使用字符串方式指定字段,并直接映射到DTO
|
||||||
|
*
|
||||||
|
* @param departmentName 部门名称过滤条件
|
||||||
|
* @param projectDeleted 项目是否已删除的过滤条件
|
||||||
|
* @return 员工DTO列表
|
||||||
|
*/
|
||||||
|
public List<EmployeeDTO> getEmployeeDetails(String departmentName, boolean projectDeleted) {
|
||||||
|
// 创建查询构建器,从Employee实体开始
|
||||||
|
JoinBuilder<Employee> builder = JoinBuilder.from(Employee.class);
|
||||||
|
|
||||||
|
// 添加关联,使用字符串指定实体间的关系字段
|
||||||
|
builder.join(Department.class, "departmentId", "id");
|
||||||
|
builder.join(Project.class, "projectId", "id");
|
||||||
|
|
||||||
|
// 添加查询条件,使用字符串指定字段
|
||||||
|
if (departmentName != null && !departmentName.isEmpty()) {
|
||||||
|
builder.equal(Department.class, "name", departmentName);
|
||||||
|
}
|
||||||
|
builder.equal(Project.class, "deleted", projectDeleted);
|
||||||
|
|
||||||
|
|
||||||
|
// 使用selectAs设置字段别名,避免使用FieldMapping
|
||||||
|
builder.selectAs("employeeId", Employee.class, "id");
|
||||||
|
builder.selectAs("employeeName", Employee.class, "name");
|
||||||
|
builder.selectAs("departmentName", Department.class, "name");
|
||||||
|
builder.selectAs("projectName", Project.class, "name");
|
||||||
|
|
||||||
|
// 直接执行查询并映射到DTO
|
||||||
|
return builder.executeAndMap(entityManager, EmployeeDTO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用自定义条件查询员工信息
|
||||||
|
* 使用selectAs来指定字段别名对应DTO构造函数参数
|
||||||
|
*
|
||||||
|
* @param employeeName 员工名称过滤条件,可选
|
||||||
|
* @param departmentName 部门名称过滤条件,可选
|
||||||
|
* @return 员工DTO列表
|
||||||
|
*/
|
||||||
|
public List<EmployeeDTO> findEmployees(String employeeName, String departmentName) {
|
||||||
|
JoinBuilder<Employee> builder = JoinBuilder.from(Employee.class);
|
||||||
|
|
||||||
|
// 添加关联,使用字符串
|
||||||
|
builder.join(Department.class, "departmentId", "id");
|
||||||
|
builder.join(Project.class, "projectId", "id");
|
||||||
|
|
||||||
|
// 添加查询条件,使用字符串
|
||||||
|
if (employeeName != null && !employeeName.isEmpty()) {
|
||||||
|
builder.equal(Employee.class, "name", employeeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (departmentName != null && !departmentName.isEmpty()) {
|
||||||
|
builder.equal(Department.class, "name", departmentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2:使用selectAs直接指定字段别名
|
||||||
|
builder.selectAs("employeeId", Employee.class, "id");
|
||||||
|
builder.selectAs("employeeName", Employee.class, "name");
|
||||||
|
builder.selectAs("departmentName", Department.class, "name");
|
||||||
|
builder.selectAs("projectName", Project.class, "name");
|
||||||
|
|
||||||
|
// 直接执行查询并映射到DTO
|
||||||
|
return builder.executeAndMap(entityManager, EmployeeDTO.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.guwan.backend.jpaTest;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "projects")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Project {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Boolean deleted = false;
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package com.guwan.backend.jpaTest.mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询结果映射工具类
|
||||||
|
* 用于将原始查询结果转换为更有意义的对象
|
||||||
|
*/
|
||||||
|
public class QueryResultMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Object[]结果列表映射为指定类型的对象列表
|
||||||
|
* @param results 查询结果
|
||||||
|
* @param mapper 映射函数
|
||||||
|
* @return 映射后的对象列表
|
||||||
|
*/
|
||||||
|
public static <T> List<T> mapToList(List<Object[]> results, Function<ResultRow, T> mapper) {
|
||||||
|
return results.stream()
|
||||||
|
.map(row -> mapper.apply(new ResultRow(row)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Object[]结果列表使用命名方式映射为指定类型的对象列表
|
||||||
|
* @param results 查询结果
|
||||||
|
* @param columnMap 列名到索引的映射
|
||||||
|
* @param mapper 映射函数
|
||||||
|
* @return 映射后的对象列表
|
||||||
|
*/
|
||||||
|
public static <T> List<T> mapToListWithNames(List<Object[]> results,
|
||||||
|
Map<String, Integer> columnMap,
|
||||||
|
Function<NamedResultRow, T> mapper) {
|
||||||
|
return results.stream()
|
||||||
|
.map(row -> mapper.apply(new NamedResultRow(row, columnMap)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结果行封装,提供类型安全的访问方法
|
||||||
|
*/
|
||||||
|
public static class ResultRow {
|
||||||
|
private final Object[] row;
|
||||||
|
|
||||||
|
public ResultRow(Object[] row) {
|
||||||
|
this.row = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLong(int index) {
|
||||||
|
Object value = getValue(index);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Long) return (Long) value;
|
||||||
|
if (value instanceof Number) return ((Number) value).longValue();
|
||||||
|
return Long.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInteger(int index) {
|
||||||
|
Object value = getValue(index);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Integer) return (Integer) value;
|
||||||
|
if (value instanceof Number) return ((Number) value).intValue();
|
||||||
|
return Integer.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(int index) {
|
||||||
|
Object value = getValue(index);
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getBoolean(int index) {
|
||||||
|
Object value = getValue(index);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Boolean) return (Boolean) value;
|
||||||
|
if (value instanceof Number) return ((Number) value).intValue() != 0;
|
||||||
|
return Boolean.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue(int index) {
|
||||||
|
if (index < 0 || index >= row.length) {
|
||||||
|
throw new IndexOutOfBoundsException("索引超出范围: " + index);
|
||||||
|
}
|
||||||
|
return row[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命名结果行封装,通过列名访问数据
|
||||||
|
*/
|
||||||
|
public static class NamedResultRow {
|
||||||
|
private final Object[] row;
|
||||||
|
private final Map<String, Integer> columnMap;
|
||||||
|
|
||||||
|
public NamedResultRow(Object[] row, Map<String, Integer> columnMap) {
|
||||||
|
this.row = row;
|
||||||
|
this.columnMap = columnMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLong(String columnName) {
|
||||||
|
Object value = getValue(columnName);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Long) return (Long) value;
|
||||||
|
if (value instanceof Number) return ((Number) value).longValue();
|
||||||
|
return Long.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInteger(String columnName) {
|
||||||
|
Object value = getValue(columnName);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Integer) return (Integer) value;
|
||||||
|
if (value instanceof Number) return ((Number) value).intValue();
|
||||||
|
return Integer.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String columnName) {
|
||||||
|
Object value = getValue(columnName);
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getBoolean(String columnName) {
|
||||||
|
Object value = getValue(columnName);
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Boolean) return (Boolean) value;
|
||||||
|
if (value instanceof Number) return ((Number) value).intValue() != 0;
|
||||||
|
return Boolean.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue(String columnName) {
|
||||||
|
Integer index = columnMap.get(columnName);
|
||||||
|
if (index == null) {
|
||||||
|
throw new IllegalArgumentException("列名不存在: " + columnName);
|
||||||
|
}
|
||||||
|
if (index < 0 || index >= row.length) {
|
||||||
|
throw new IndexOutOfBoundsException("索引超出范围: " + index);
|
||||||
|
}
|
||||||
|
return row[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
# MySQL数据库配置
|
||||||
|
spring.datasource.url=jdbc:mysql://localhost:3306/jpatest?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
|
||||||
|
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
|
||||||
|
spring.datasource.username=root
|
||||||
|
spring.datasource.password=123456
|
||||||
|
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
|
||||||
|
|
||||||
|
# JPA配置
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.show-sql=true
|
||||||
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
server.port=8080
|
||||||
|
|
||||||
|
# 日志级别
|
||||||
|
logging.level.org.hibernate.SQL=DEBUG
|
||||||
|
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
|
Loading…
Reference in New Issue