1
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.entity.Author;
|
||||
import com.example.demo.repository.AuthorRepository;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/authors")
|
||||
public class AuthorController extends BaseController<Author, Long> {
|
||||
|
||||
public AuthorController(AuthorRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import com.example.demo.repository.BaseRepository;
|
||||
import com.example.demo.repository.GenericSpecification;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
@@ -42,6 +42,55 @@ public abstract class BaseController<T, ID extends Serializable> {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public T get(@PathVariable ID id) {
|
||||
return repository.findById(id).orElseThrow(() -> new RuntimeException("Entity not found"));
|
||||
return repository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Entity not found"));
|
||||
}
|
||||
|
||||
// --- Write Operations ---
|
||||
|
||||
@PostMapping
|
||||
public T create(@RequestBody T entity) {
|
||||
// JPA Cascade Persist will handle nested objects if configured
|
||||
return repository.save(entity);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public T update(@PathVariable ID id, @RequestBody T entity) {
|
||||
// Ensure ID is set
|
||||
// Note: This is a "save/merge" operation.
|
||||
// If entity contains null fields, they might overwrite DB values depending on how the entity was constructed.
|
||||
// Typically PUT implies full replacement.
|
||||
// For partial update, use PATCH.
|
||||
|
||||
// We can't easily set ID on generic T without reflection or interface,
|
||||
// assuming entity already has ID or we rely on repository.save behavior.
|
||||
// Ideally, we should check if ID exists.
|
||||
if (!repository.existsById(id)) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Entity not found");
|
||||
}
|
||||
// In a real generic controller, we would force entity.setId(id).
|
||||
// Here we assume the body contains the correct ID or we trust the user.
|
||||
return repository.save(entity);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public T patch(@PathVariable ID id, @RequestBody Map<String, Object> updates) throws Exception {
|
||||
T dbEntity = repository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Entity not found"));
|
||||
|
||||
// Create a copy of ObjectMapper to avoid side effects on the global bean
|
||||
ObjectMapper patchMapper = objectMapper.copy();
|
||||
|
||||
// Configure ObjectMapper to merge nested objects instead of replacing them
|
||||
// This simulates "Deep Merge" for PATCH operations
|
||||
patchMapper.setDefaultMergeable(true);
|
||||
|
||||
// Magic of Jackson: Update existing object with map values
|
||||
patchMapper.readerForUpdating(dbEntity).readValue(patchMapper.writeValueAsString(updates));
|
||||
|
||||
return repository.save(dbEntity);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable ID id) {
|
||||
repository.deleteById(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,21 @@ public class BookController extends BaseController<Book, Long> {
|
||||
this.bookService = bookService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Book createBook(@RequestParam String title,
|
||||
@RequestParam String isbn,
|
||||
@RequestParam String author,
|
||||
@RequestParam String publisher) {
|
||||
return bookService.createBook(title, isbn, author, publisher);
|
||||
}
|
||||
// BaseController provides:
|
||||
// POST /books (create)
|
||||
// GET /books (list with dynamic filter)
|
||||
// GET /books/{id} (get one)
|
||||
// PATCH /books/{id} (partial update)
|
||||
// DELETE /books/{id} (delete)
|
||||
|
||||
// Override the base list method to map it to /jpa specifically, or just use the base one
|
||||
// Here we expose the generic list capability at /jpa endpoint to match previous behavior
|
||||
// Keep this for backward compatibility with tests using /jpa endpoint
|
||||
@GetMapping("/jpa")
|
||||
public Object getAllJPA(@RequestParam(required = false) java.util.Map<String, String> allParams) {
|
||||
return super.list(allParams);
|
||||
}
|
||||
|
||||
// Original methods for comparison
|
||||
@GetMapping("/jpa/{id}")
|
||||
public Book getBookById(@PathVariable Long id) {
|
||||
public Book getBookByIdJPA(@PathVariable Long id) {
|
||||
return super.get(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Book {
|
||||
private String title;
|
||||
private String isbn;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
|
||||
@JoinColumn(name = "author_id")
|
||||
private Author author;
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@ package com.example.demo.repository;
|
||||
import com.example.demo.entity.Author;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface AuthorRepository extends JpaRepository<Author, Long> {
|
||||
public interface AuthorRepository extends BaseRepository<Author, Long> {
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PeriodicalRepository extends JpaRepository<Periodical, Long> {
|
||||
public interface PeriodicalRepository extends BaseRepository<Periodical, Long> {
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ package com.example.demo.repository;
|
||||
import com.example.demo.entity.Publisher;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface PublisherRepository extends JpaRepository<Publisher, Long> {
|
||||
public interface PublisherRepository extends BaseRepository<Publisher, Long> {
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface RegionRepository extends JpaRepository<Region, Long> {
|
||||
public interface RegionRepository extends BaseRepository<Region, Long> {
|
||||
}
|
||||
|
||||
401
src/main/java/com/example/demo/util/MyBatisGeneratorUtils.java
Normal file
401
src/main/java/com/example/demo/util/MyBatisGeneratorUtils.java
Normal file
@@ -0,0 +1,401 @@
|
||||
package com.example.demo.util;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinColumns;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MyBatisGeneratorUtils {
|
||||
|
||||
private static final String SEPARATOR = "$";
|
||||
|
||||
public static String generateSql(Class<?> rootClass, List<String> fields) {
|
||||
// 1. Parse the fields to build a Join Tree
|
||||
// Use entity name (lowercase) as root alias, e.g., "book"
|
||||
String rootAlias = rootClass.getSimpleName().toLowerCase();
|
||||
JoinNode rootNode = new JoinNode(rootClass, rootAlias);
|
||||
Set<String> selectColumns = new LinkedHashSet<>();
|
||||
|
||||
// 0. Force Select ID for Root (Critical for Collection Mapping)
|
||||
addIdColumn(rootNode, selectColumns, rootAlias);
|
||||
|
||||
for (String fieldPath : fields) {
|
||||
parseFieldPath(rootNode, fieldPath, selectColumns, rootAlias);
|
||||
}
|
||||
|
||||
// 2. Generate SELECT clause
|
||||
StringBuilder sql = new StringBuilder("SELECT \n");
|
||||
sql.append(String.join(", \n", selectColumns));
|
||||
|
||||
// 3. Generate FROM clause
|
||||
String tableName = getTableName(rootClass);
|
||||
sql.append("\nFROM ").append(tableName).append(" ").append(rootNode.alias);
|
||||
|
||||
// 4. Generate JOIN clauses (Recursively)
|
||||
StringBuilder joins = new StringBuilder();
|
||||
generateJoins(rootNode, joins);
|
||||
sql.append(joins);
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private static void addIdColumn(JoinNode node, Set<String> selectColumns, String rootAlias) {
|
||||
Field idField = getIdField(node.entityClass);
|
||||
if (idField != null) {
|
||||
String columnName = getColumnName(node.entityClass, idField.getName());
|
||||
String columnAlias = getColumnAlias(node.alias, idField.getName(), rootAlias, columnName);
|
||||
selectColumns.add(" " + node.alias + "." + columnName + " AS " + columnAlias);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseFieldPath(JoinNode currentNode, String fieldPath, Set<String> selectColumns, String rootAlias) {
|
||||
if (!fieldPath.contains(".")) {
|
||||
// Check if this is a relation field (Object or Collection) or a simple field
|
||||
try {
|
||||
Field field = currentNode.entityClass.getDeclaredField(fieldPath);
|
||||
|
||||
// Case 1: Simple Field
|
||||
if (isSimpleField(field)) {
|
||||
String columnName = getColumnName(currentNode.entityClass, fieldPath);
|
||||
String columnAlias = getColumnAlias(currentNode.alias, fieldPath, rootAlias, columnName);
|
||||
selectColumns.add(" " + currentNode.alias + "." + columnName + " AS " + columnAlias);
|
||||
}
|
||||
// Case 2: Relation Field (Object or Collection) - Expand all columns
|
||||
else {
|
||||
// Create child node for this relation
|
||||
Class<?> targetClass = getTargetClass(field); // Use helper
|
||||
|
||||
String relationName = fieldPath;
|
||||
JoinNode childNode = currentNode.children.computeIfAbsent(relationName, k -> {
|
||||
// Alias: book$author
|
||||
String childAlias = currentNode.alias + SEPARATOR + relationName;
|
||||
JoinNode newNode = new JoinNode(targetClass, childAlias, field);
|
||||
// Force Select ID for Child (Critical for Collection Mapping)
|
||||
addIdColumn(newNode, selectColumns, rootAlias);
|
||||
return newNode;
|
||||
});
|
||||
|
||||
// Expand all simple columns of target entity
|
||||
for (Field f : targetClass.getDeclaredFields()) {
|
||||
if (isSimpleField(f)) {
|
||||
String colName = getColumnName(targetClass, f.getName());
|
||||
// Alias: book$author$name
|
||||
String columnAlias = getColumnAlias(childNode.alias, f.getName(), rootAlias, colName);
|
||||
selectColumns.add(" " + childNode.alias + "." + colName + " AS " + columnAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Ignore or handle error
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Split "author.name" -> "author" and "name"
|
||||
String[] parts = fieldPath.split("\\.", 2);
|
||||
String relationField = parts[0];
|
||||
String remainingPath = parts[1];
|
||||
|
||||
// Find or create child node for this relation
|
||||
JoinNode childNode = currentNode.children.computeIfAbsent(relationField, k -> {
|
||||
Field field = getField(currentNode.entityClass, relationField);
|
||||
Class<?> type = getTargetClass(field); // Use helper
|
||||
|
||||
// Alias: book$author
|
||||
String childAlias = currentNode.alias + SEPARATOR + relationField;
|
||||
JoinNode newNode = new JoinNode(type, childAlias, field);
|
||||
// Force Select ID for Child
|
||||
addIdColumn(newNode, selectColumns, rootAlias);
|
||||
return newNode;
|
||||
});
|
||||
|
||||
// Recursively parse the rest
|
||||
parseFieldPath(childNode, remainingPath, selectColumns, rootAlias);
|
||||
}
|
||||
|
||||
private static String getColumnAlias(String nodeAlias, String fieldName, String rootAlias, String columnName) {
|
||||
// Full alias path should use columnName for the last part to match ResultMap definition
|
||||
// Example: book$author$publish_month (instead of publishMonth)
|
||||
String fullPath = nodeAlias + SEPARATOR + columnName;
|
||||
|
||||
// If node is root (alias "book"), we want "title", not "book$title"
|
||||
// If node is child ("book$author"), we want "author$name", not "book$author$name"
|
||||
|
||||
if (fullPath.startsWith(rootAlias + SEPARATOR)) {
|
||||
return fullPath.substring(rootAlias.length() + SEPARATOR.length());
|
||||
}
|
||||
return fullPath; // Should not happen if rootAlias is correct
|
||||
}
|
||||
|
||||
// ... existing code ...
|
||||
|
||||
private static String buildJoinCondition(JoinNode parent, JoinNode child) {
|
||||
Field relationField = child.relationFieldFromParent;
|
||||
|
||||
// Handle @OneToMany(mappedBy="...")
|
||||
if (relationField.isAnnotationPresent(jakarta.persistence.OneToMany.class)) {
|
||||
jakarta.persistence.OneToMany oneToMany = relationField.getAnnotation(jakarta.persistence.OneToMany.class);
|
||||
String mappedBy = oneToMany.mappedBy();
|
||||
if (!mappedBy.isEmpty()) {
|
||||
// Parent: Author (id), Child: Book (author_id)
|
||||
// mappedBy = "author" -> Child has field "author"
|
||||
// Condition: parent.id = child.author_id
|
||||
|
||||
// We need to find the column name for "author" in Child entity
|
||||
// Convention: mappedBy + "_id"
|
||||
String childJoinColumn = mappedBy + "_id";
|
||||
|
||||
// Try to find @JoinColumn on the child side field
|
||||
try {
|
||||
Field childField = child.entityClass.getDeclaredField(mappedBy);
|
||||
if (childField.isAnnotationPresent(JoinColumn.class)) {
|
||||
childJoinColumn = childField.getAnnotation(JoinColumn.class).name();
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
// ignore, use convention
|
||||
}
|
||||
|
||||
return parent.alias + ".id = " + child.alias + "." + childJoinColumn;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle @JoinColumn (ManyToOne / OneToOne)
|
||||
if (relationField.isAnnotationPresent(JoinColumn.class)) {
|
||||
// ... existing logic ...
|
||||
JoinColumn joinColumn = relationField.getAnnotation(JoinColumn.class);
|
||||
String foreignKey = joinColumn.name();
|
||||
String referencedColumn = joinColumn.referencedColumnName();
|
||||
if (referencedColumn.isEmpty()) referencedColumn = "id";
|
||||
|
||||
return parent.alias + "." + foreignKey + " = " + child.alias + "." + referencedColumn;
|
||||
}
|
||||
|
||||
// Handle @JoinColumns (Composite Key)
|
||||
if (relationField.isAnnotationPresent(JoinColumns.class)) {
|
||||
// ... existing logic ...
|
||||
JoinColumns joinColumns = relationField.getAnnotation(JoinColumns.class);
|
||||
return Arrays.stream(joinColumns.value())
|
||||
.map(jc -> {
|
||||
String refCol = jc.referencedColumnName();
|
||||
if (refCol.isEmpty()) refCol = "id";
|
||||
return parent.alias + "." + jc.name() + " = " + child.alias + "." + refCol;
|
||||
})
|
||||
.collect(Collectors.joining(" AND "));
|
||||
}
|
||||
|
||||
// Fallback (Convention: fieldName_id)
|
||||
return parent.alias + "." + relationField.getName() + "_id = " + child.alias + ".id";
|
||||
}
|
||||
|
||||
// --- XML Generation ---
|
||||
|
||||
public static String generateMapperXml(Class<?> rootClass, String namespace, List<String> fields) {
|
||||
StringBuilder xml = new StringBuilder();
|
||||
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
|
||||
xml.append("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n");
|
||||
xml.append("<mapper namespace=\"").append(namespace).append("\">\n");
|
||||
|
||||
// 1. Identify all involved classes
|
||||
Set<Class<?>> involvedClasses = new LinkedHashSet<>();
|
||||
collectInvolvedClasses(rootClass, fields, involvedClasses);
|
||||
|
||||
// 2. Generate ResultMap for each class
|
||||
for (Class<?> clazz : involvedClasses) {
|
||||
xml.append(generateResultMap(clazz, involvedClasses)).append("\n");
|
||||
}
|
||||
|
||||
// 3. Generate Select Statement
|
||||
xml.append(" <select id=\"selectCustom\" resultMap=\"").append(rootClass.getSimpleName()).append("Map\">\n");
|
||||
String sql = generateSql(rootClass, fields);
|
||||
// Indent SQL
|
||||
xml.append(indent(sql, 8)).append("\n");
|
||||
xml.append(" </select>\n");
|
||||
|
||||
xml.append("</mapper>");
|
||||
return xml.toString();
|
||||
}
|
||||
|
||||
private static void collectInvolvedClasses(Class<?> rootClass, List<String> fields, Set<Class<?>> involvedClasses) {
|
||||
involvedClasses.add(rootClass);
|
||||
JoinNode rootNode = new JoinNode(rootClass, "root"); // alias doesn't matter here
|
||||
for (String fieldPath : fields) {
|
||||
parseFieldPathForClasses(rootNode, fieldPath, involvedClasses);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseFieldPathForClasses(JoinNode currentNode, String fieldPath, Set<Class<?>> involvedClasses) {
|
||||
if (!fieldPath.contains(".")) {
|
||||
try {
|
||||
Field field = currentNode.entityClass.getDeclaredField(fieldPath);
|
||||
if (!isSimpleField(field)) {
|
||||
Class<?> targetClass = getTargetClass(field);
|
||||
involvedClasses.add(targetClass);
|
||||
}
|
||||
} catch (NoSuchFieldException e) { }
|
||||
return;
|
||||
}
|
||||
|
||||
String[] parts = fieldPath.split("\\.", 2);
|
||||
String relationField = parts[0];
|
||||
String remainingPath = parts[1];
|
||||
|
||||
try {
|
||||
Field field = getField(currentNode.entityClass, relationField);
|
||||
Class<?> targetClass = getTargetClass(field);
|
||||
involvedClasses.add(targetClass);
|
||||
|
||||
JoinNode childNode = currentNode.children.computeIfAbsent(relationField, k -> new JoinNode(targetClass, "child"));
|
||||
parseFieldPathForClasses(childNode, remainingPath, involvedClasses);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
private static Class<?> getTargetClass(Field field) {
|
||||
Class<?> type = field.getType();
|
||||
if (Collection.class.isAssignableFrom(type)) {
|
||||
java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) field.getGenericType();
|
||||
return (Class<?>) pt.getActualTypeArguments()[0];
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static String generateResultMap(Class<?> clazz, Set<Class<?>> involvedClasses) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String mapId = clazz.getSimpleName() + "Map";
|
||||
sb.append(" <resultMap id=\"").append(mapId).append("\" type=\"").append(clazz.getName()).append("\">\n");
|
||||
|
||||
// ID Field
|
||||
Field idField = getIdField(clazz);
|
||||
if (idField != null) {
|
||||
String colName = getColumnName(clazz, idField.getName());
|
||||
sb.append(" <id property=\"").append(idField.getName()).append("\" column=\"").append(colName).append("\"/>\n");
|
||||
}
|
||||
|
||||
// Other Fields
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.equals(idField)) continue;
|
||||
|
||||
if (isSimpleField(field)) {
|
||||
String colName = getColumnName(clazz, field.getName());
|
||||
sb.append(" <result property=\"").append(field.getName()).append("\" column=\"").append(colName).append("\"/>\n");
|
||||
} else {
|
||||
// Association or Collection
|
||||
Class<?> targetClass = getTargetClass(field);
|
||||
// Only generate if target class is in our involved list (or we could generate all, but that risks infinite recursion if graph is large)
|
||||
// For "Complete XML", let's include it if it's in the involved list to ensure we have the maps.
|
||||
if (involvedClasses.contains(targetClass)) {
|
||||
String targetMapId = targetClass.getSimpleName() + "Map";
|
||||
String prefix = field.getName() + SEPARATOR;
|
||||
|
||||
if (Collection.class.isAssignableFrom(field.getType())) {
|
||||
sb.append(" <collection property=\"").append(field.getName())
|
||||
.append("\" resultMap=\"").append(targetMapId)
|
||||
.append("\" columnPrefix=\"").append(prefix).append("\"/>\n");
|
||||
} else {
|
||||
sb.append(" <association property=\"").append(field.getName())
|
||||
.append("\" resultMap=\"").append(targetMapId)
|
||||
.append("\" columnPrefix=\"").append(prefix).append("\"/>\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append(" </resultMap>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static Field getIdField(Class<?> clazz) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(jakarta.persistence.Id.class)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
// Fallback: look for "id"
|
||||
try {
|
||||
return clazz.getDeclaredField("id");
|
||||
} catch (NoSuchFieldException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String indent(String text, int spaces) {
|
||||
String indent = new String(new char[spaces]).replace("\0", " ");
|
||||
return text.replaceAll("(?m)^", indent); // Multiline regex
|
||||
}
|
||||
|
||||
// --- Helper Classes & Methods ---
|
||||
|
||||
private static void generateJoins(JoinNode node, StringBuilder sb) {
|
||||
for (Map.Entry<String, JoinNode> entry : node.children.entrySet()) {
|
||||
JoinNode child = entry.getValue();
|
||||
|
||||
// Determine Join Condition
|
||||
// Assumption: @JoinColumn is on the parent entity's field (ManyToOne/OneToOne)
|
||||
// parent.author_id = child.id
|
||||
String joinCondition = buildJoinCondition(node, child);
|
||||
String tableName = getTableName(child.entityClass);
|
||||
|
||||
sb.append("\nLEFT JOIN ").append(tableName).append(" ").append(child.alias)
|
||||
.append(" ON ").append(joinCondition);
|
||||
|
||||
// Recursively generate joins for children
|
||||
generateJoins(child, sb);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSimpleField(Field field) {
|
||||
// Simple check: String, Number, Date, etc. or Primitive
|
||||
Class<?> type = field.getType();
|
||||
return type.isPrimitive() ||
|
||||
String.class.isAssignableFrom(type) ||
|
||||
Number.class.isAssignableFrom(type) ||
|
||||
Boolean.class.isAssignableFrom(type) ||
|
||||
java.util.Date.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
private static class JoinNode {
|
||||
Class<?> entityClass;
|
||||
String alias;
|
||||
Field relationFieldFromParent; // The field in parent that points to this entity
|
||||
Map<String, JoinNode> children = new LinkedHashMap<>();
|
||||
|
||||
public JoinNode(Class<?> entityClass, String alias) {
|
||||
this.entityClass = entityClass;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public JoinNode(Class<?> entityClass, String alias, Field relationFieldFromParent) {
|
||||
this(entityClass, alias);
|
||||
this.relationFieldFromParent = relationFieldFromParent;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTableName(Class<?> clazz) {
|
||||
if (clazz.isAnnotationPresent(Table.class)) {
|
||||
String name = clazz.getAnnotation(Table.class).name();
|
||||
if (!name.isEmpty()) return name;
|
||||
}
|
||||
return clazz.getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
private static String getColumnName(Class<?> clazz, String fieldName) {
|
||||
Field field = getField(clazz, fieldName);
|
||||
if (field.isAnnotationPresent(Column.class)) {
|
||||
String name = field.getAnnotation(Column.class).name();
|
||||
if (!name.isEmpty()) return name;
|
||||
}
|
||||
return fieldName; // Default: field name is column name (simplified)
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException("Field not found: " + clazz.getName() + "." + fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user