Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1단계 - OneToMany (FetchType.EAGER) #128

Open
wants to merge 8 commits into
base: tmddnrdl333
Choose a base branch
from
40 changes: 40 additions & 0 deletions src/main/java/example/entity/Order.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package example.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

import java.util.List;

@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNumber;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "order_id", referencedColumnName = "order_id")
private List<OrderItem> orderItems;

public String getOrderNumber() {
return orderNumber;
}

public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}

// public List<OrderItem> getOrderItems() {
// return orderItems;
// }
//
// public void setOrderItems(List<OrderItem> orderItems) {
// this.orderItems = orderItems;
// }
}
44 changes: 44 additions & 0 deletions src/main/java/example/entity/OrderItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package example.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private Integer quantity;
@Column(name = "order_id")
private Long orderId;
Comment on lines +18 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrderOrderItem 클래스를 추가해주셨네요 👍

다만, 기존 요구사항에는 OrderItem 클래스에는 orderId 필드가 존재하지 않아요 😢

실제 JPA 를 사용할 때, 부모 entity 에는 정보가 있지만 자식 entity 에는 부모 정보가 없거나 반대의 경우도 있는데요! 해당 부분을 해결하려면 어떻게 해야할까요?


public String getProduct() {
return product;
}

public void setProduct(String product) {
this.product = product;
}

public Integer getQuantity() {
return quantity;
}

public void setQuantity(Integer quantity) {
this.quantity = quantity;
}

public Long getOrderId() {
return orderId;
}

public void setOrderId(Long orderId) {
this.orderId = orderId;
}
}
4 changes: 3 additions & 1 deletion src/main/java/jdbc/EntityRowMapper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package jdbc;

import jakarta.persistence.JoinColumn;
import jakarta.persistence.Transient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,7 +24,8 @@ public T mapRow(ResultSet resultSet) {
T entity = this.entityClass.getDeclaredConstructor().newInstance();

for (Field field : this.entityClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Transient.class)) {
if (field.isAnnotationPresent(Transient.class)
|| field.isAnnotationPresent(JoinColumn.class)) {
continue;
}
field.setAccessible(true);
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/jdbc/JdbcTemplate.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package jdbc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class JdbcTemplate {
private static final Logger logger = LoggerFactory.getLogger(JdbcTemplate.class);
private final Connection connection;

public JdbcTemplate(final Connection connection) {
Expand All @@ -19,6 +23,7 @@ public void execute(final String sql) {
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.debug("executed query : {}", sql);
}

public Long executeAndReturnGeneratedKey(final String sql) {
Expand All @@ -32,6 +37,7 @@ public Long executeAndReturnGeneratedKey(final String sql) {
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.debug("executed query : {}", sql);
return null;
}

Expand All @@ -40,10 +46,11 @@ public <T> T queryForObject(final String sql, final RowMapper<T> rowMapper) {
if (results.size() != 1) {
throw new RuntimeException("Expected 1 result, got " + results.size());
}
logger.debug("executed query : {}", sql);
return results.get(0);
}

public <T> List<T> query(final String sql, final RowMapper<T> rowMapper) {
private <T> List<T> query(final String sql, final RowMapper<T> rowMapper) {
try (final ResultSet resultSet = connection.prepareStatement(sql).executeQuery()) {
final List<T> result = new ArrayList<>();
while (resultSet.next()) {
Expand Down
9 changes: 3 additions & 6 deletions src/main/java/persistence/EntityScanner.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package persistence;

import jakarta.persistence.Entity;
import jakarta.persistence.Transient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import persistence.entity.EntityUtils;
import persistence.sql.NameUtils;
import persistence.sql.ddl.create.CreateQueryBuilder;
import persistence.sql.ddl.create.component.column.ColumnComponentBuilder;
Expand Down Expand Up @@ -77,12 +77,9 @@ public List<String> getDdlDropQueries() {
}

private String generateDdlCreateQuery(Class<?> entityClass) {
Field[] fields = entityClass.getDeclaredFields();
CreateQueryBuilder queryBuilder = CreateQueryBuilder.newInstance();
for (Field field : fields) {
if (field.isAnnotationPresent(Transient.class)) {
continue;
}

for (Field field : EntityUtils.getManagedFields(entityClass)) {
queryBuilder.add(ColumnComponentBuilder.from(field));
queryBuilder.add(ConstraintComponentBuilder.from(field));
}
Expand Down
54 changes: 51 additions & 3 deletions src/main/java/persistence/entity/EntityLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,65 @@

import jdbc.EntityRowMapper;
import jdbc.JdbcTemplate;
import persistence.sql.component.ColumnInfo;
import persistence.sql.component.ConditionBuilder;
import persistence.sql.component.JoinCondition;
import persistence.sql.component.JoinConditionBuilder;
import persistence.sql.component.JoinInfo;
import persistence.sql.component.JoinType;
import persistence.sql.component.TableInfo;
import persistence.sql.dml.select.SelectQueryBuilder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class EntityLoader {
private final JdbcTemplate jdbcTemplate;

public EntityLoader(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public Object find(Class<?> clazz, Long id) {
String findByIdQuery = SelectQueryBuilder.generateQuery(clazz, id);
return jdbcTemplate.queryForObject(findByIdQuery, new EntityRowMapper<>(clazz));
public Object find(Class<?> entityClass, Long id) {
TableInfo fromTable = TableInfo.from(entityClass);
ColumnInfo idColumn = fromTable.getIdColumn();

SelectQueryBuilder selectQueryBuilder = new SelectQueryBuilder()
.fromTableInfo(fromTable);

List<JoinCondition> joinConditions = getJoinConditions(fromTable);
if (!joinConditions.isEmpty()) {
selectQueryBuilder
.joinConditions(joinConditions);
}

selectQueryBuilder
.whereCondition(
new ConditionBuilder()
.columnInfo(idColumn)
.values(Collections.singletonList(id.toString()))
.build()
);

String query = selectQueryBuilder.build().toString();
return jdbcTemplate.queryForObject(query, new EntityRowMapper<>(entityClass));
}

private List<JoinCondition> getJoinConditions(TableInfo tableInfo) {
List<JoinCondition> joinConditions = new ArrayList<>();

List<JoinInfo> joinInfos = tableInfo.getJoinInfos();
for (JoinInfo joinInfo : joinInfos) {
JoinCondition joinCondition = new JoinConditionBuilder()
.joinType(JoinType.LEFT_JOIN)
.tableInfo(joinInfo.getTargetColumnInfo().getTableInfo())
.sourceColumnInfo(joinInfo.getSourceColumnInfo())
.targetColumnInfo(joinInfo.getTargetColumnInfo())
.build();
joinConditions.add(joinCondition);
}

return joinConditions;
}
}
6 changes: 3 additions & 3 deletions src/main/java/persistence/entity/EntityManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ private void executeEntityActions(List<EntityEntry> entryList) {
}

@Override
public Object find(Class<?> clazz, Long id) {
public Object find(Class<?> entityClass, Long id) {
if (id == null) {
throw new IllegalArgumentException("Unable to find without an ID!");
}

/* 관리 중인 엔티티라면 영속성 컨텍스트에서 꺼내서 반환 */
Object managedEntity = persistenceContext.getEntity(clazz, id);
Object managedEntity = persistenceContext.getEntity(entityClass, id);
if (managedEntity != null) {
return managedEntity;
}

/* 관리 중이지 않다면 DB 조회 */
Object entity = entityLoader.find(clazz, id);
Object entity = entityLoader.find(entityClass, id);
persistenceContext.putEntity(entity);
return entity;
}
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/persistence/entity/EntityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Transient;

import java.lang.reflect.Field;
Expand All @@ -25,15 +26,25 @@ public static Field[] getManagedFields(Class<?> clazz) {
.toArray(Field[]::new);
}

public static Field[] getManagedFieldsExceptId(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields())
.filter(EntityUtils::isManagedField)
.filter(field -> !field.isAnnotationPresent(Id.class))
.toArray(Field[]::new);
}

private static boolean isManagedField(Field field) {
if (field.isAnnotationPresent(Transient.class)) {
return false;
}
if (field.isAnnotationPresent(Id.class)
&& field.isAnnotationPresent(GeneratedValue.class)
&& GenerationType.IDENTITY.equals(field.getAnnotation(GeneratedValue.class).strategy())) {
if (field.isAnnotationPresent(JoinColumn.class)) {
return false;
}
// if (field.isAnnotationPresent(Id.class)
// && field.isAnnotationPresent(GeneratedValue.class)
// && GenerationType.IDENTITY.equals(field.getAnnotation(GeneratedValue.class).strategy())) {
// return false;
// }
return true;
}

Expand Down
1 change: 1 addition & 0 deletions src/main/java/persistence/sql/NameUtils.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package persistence.sql;

import jakarta.persistence.Column;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;

import java.lang.reflect.Field;
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/persistence/sql/component/ColumnInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package persistence.sql.component;

import persistence.sql.NameUtils;

import java.lang.reflect.Field;

public class ColumnInfo {
private TableInfo tableInfo;
private Field columnField;

private ColumnInfo(TableInfo tableInfo, Field field) {
this.tableInfo = tableInfo;
this.columnField = field;
}

public static ColumnInfo of(TableInfo tableInfo, Field columnField) {
return new ColumnInfo(tableInfo, columnField);
}

public TableInfo getTableInfo() {
return tableInfo;
}

public Field getColumnField() {
return columnField;
}
Comment on lines +20 to +26
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그런 의미에서 단순히 getter 를 이용하는것보단 좀 더 책임을 줘볼 수 있을 것 같아요 :)


public String getFullName() {
return tableInfo.getTableName() + "." + NameUtils.getColumnName(columnField);
}
Comment on lines +28 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ColumnInfo 라는 클래스는 Column 에 대한 정보를 제공해주는 책임을 가질 것 으로 생각이 되는데, SQL 에 필요한 이름을 반환하는 메서드를 가지고 있네요 🤔

만약 SQL 에 대한 정책이 바뀔경우, Column 정보를 제공해주는 클래스와 쿼리빌더 양쪽 다 수정이 될 가능성이 생김으로써 단일책임원칙(SRP)가 위배 될 수 있다고 생각이 들어요.

ColumnInfo 는 단순히 Column 에 대한 정보만 제공해주고, SQL 에 필요한 부분은 쿼리빌더에게 책임을 맡겨보는건 어떨까요? :)

}
39 changes: 39 additions & 0 deletions src/main/java/persistence/sql/component/Condition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package persistence.sql.component;

import java.util.List;

public class Condition {
private ColumnInfo columnInfo;
private List<String> values;
private Condition andCondition;
private Condition orCondition;

public ColumnInfo getColumnInfo() {
return columnInfo;
}

public List<String> getValues() {
return values;
}

public Condition getAndCondition() {
return andCondition;
}

public Condition getOrCondition() {
return orCondition;
}

public Condition(ColumnInfo columnInfo, List<String> values) {
this.columnInfo = columnInfo;
this.values = values;
}

public void setAndCondition(Condition andCondition) {
this.andCondition = andCondition;
}

public void setOrCondition(Condition orCondition) {
this.orCondition = orCondition;
}
}
Loading