Skip to content

Commit 557a3d2

Browse files
committed
Task: Implement a save method of a simple ORM
* create findById * create ORM annotations * setup a simple infrastructure (init DataSource) * prep save() method as a key todo
1 parent 62ad97f commit 557a3d2

File tree

9 files changed

+225
-1
lines changed

9 files changed

+225
-1
lines changed

lesson-demo/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,18 @@
1010
<modelVersion>4.0.0</modelVersion>
1111

1212
<artifactId>lesson-demo</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>org.postgresql</groupId>
17+
<artifactId>postgresql</artifactId>
18+
<version>42.7.3</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>com.google.guava</groupId>
22+
<artifactId>guava</artifactId>
23+
<version>33.2.0-jre</version>
24+
</dependency>
25+
</dependencies>
1326

1427
</project>
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package com.bobocode;
22

3+
import com.bobocode.bibernate.OrmImpl;
4+
import com.bobocode.entity.Participant;
5+
36
public class DemoApp {
47
public static void main(String[] args) {
5-
8+
var orm = new OrmImpl("jdbc:postgresql://0.tcp.eu.ngrok.io:11026/postgres", "bobouser", "bobopass");
9+
10+
var participant = orm.findById(Participant.class, 23);
11+
System.out.println(participant);
612
}
13+
714
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.bobocode.bibernate;
2+
3+
public interface Orm {
4+
<T> T findById(Class<T> entityType, Object id);
5+
6+
void save(Object entity);
7+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.bobocode.bibernate;
2+
3+
import com.bobocode.bibernate.annotation.Column;
4+
import com.bobocode.bibernate.annotation.Entity;
5+
import com.bobocode.bibernate.annotation.Id;
6+
import com.bobocode.bibernate.annotation.Table;
7+
import com.google.common.base.CaseFormat;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.SneakyThrows;
10+
import org.postgresql.ds.PGSimpleDataSource;
11+
12+
import javax.sql.DataSource;
13+
import java.lang.reflect.Field;
14+
import java.sql.ResultSet;
15+
import java.sql.Timestamp;
16+
import java.util.Arrays;
17+
import java.util.Optional;
18+
19+
@RequiredArgsConstructor
20+
public class OrmImpl implements Orm {
21+
public static final String SELECT_FROM_TABLE_BY_COLUMN = "select * from %s where %s = ?";
22+
private final DataSource dataSource;
23+
24+
public OrmImpl(String jdbcUrl, String username, String password) {
25+
var pgSimpleDataSource = new PGSimpleDataSource();
26+
pgSimpleDataSource.setURL(jdbcUrl);
27+
pgSimpleDataSource.setUser(username);
28+
pgSimpleDataSource.setPassword(password);
29+
this.dataSource = pgSimpleDataSource;
30+
}
31+
32+
@Override
33+
@SneakyThrows
34+
public <T> T findById(Class<T> entityType, Object id) {
35+
verifyEntity(entityType);
36+
try (var connection = dataSource.getConnection()) {
37+
var selectSql = buildSqlSelectById(entityType);
38+
System.out.println("SQL: " + selectSql);
39+
try (var selectStatement = connection.prepareStatement(selectSql)) {
40+
selectStatement.setObject(1, id);
41+
var rs = selectStatement.executeQuery();
42+
if (rs.next()) {
43+
return createEntityFromResultSet(entityType, rs);
44+
} else {
45+
throw new RuntimeException("Entity not found by id =" + id);
46+
}
47+
}
48+
49+
}
50+
}
51+
52+
private void verifyEntity(Class<?> entityType) {
53+
if (!entityType.isAnnotationPresent(Entity.class)) {
54+
throw new RuntimeException(entityType.getSimpleName() + " is not an @Entity");
55+
}
56+
}
57+
58+
private String buildSqlSelectById(Class<?> entityType) {
59+
var tableName = resolveTableName(entityType);
60+
var idColumnName = resolveIdColumnName(entityType);
61+
return SELECT_FROM_TABLE_BY_COLUMN.formatted(tableName, idColumnName);
62+
}
63+
64+
private String resolveTableName(Class<?> entityType) {
65+
return Optional.ofNullable(entityType.getAnnotation(Table.class))
66+
.map(Table::value)
67+
.orElseGet(() -> underscore(entityType.getSimpleName()));
68+
}
69+
70+
private String resolveIdColumnName(Class<?> entityType) {
71+
return Arrays.stream(entityType.getDeclaredFields())
72+
.filter(field -> field.isAnnotationPresent(Id.class))
73+
.findAny()
74+
.map(this::resolveColumnName)
75+
.orElseThrow(() -> new RuntimeException("Entity " + entityType.getSimpleName() + " must have an @Id"));
76+
}
77+
78+
private String resolveColumnName(Field field) {
79+
return Optional.ofNullable(field.getAnnotation(Column.class))
80+
.map(Column::value)
81+
.orElseGet(() -> underscore(field.getName()));
82+
}
83+
84+
private String underscore(String value) {
85+
return CaseFormat.LOWER_CAMEL
86+
.converterTo(CaseFormat.LOWER_UNDERSCORE)
87+
.convert(value);
88+
}
89+
90+
@SneakyThrows
91+
private <T> T createEntityFromResultSet(Class<T> entityType, ResultSet rs) {
92+
var entity = entityType.getConstructor().newInstance();
93+
for (var field : entityType.getDeclaredFields()) {
94+
var columnName = resolveColumnName(field);
95+
var fieldValue = rs.getObject(columnName);
96+
if (fieldValue instanceof Timestamp timestamp) {
97+
fieldValue = timestamp.toLocalDateTime();
98+
}
99+
field.setAccessible(true);
100+
field.set(entity, fieldValue);
101+
}
102+
return entity;
103+
}
104+
105+
/**
106+
* Saves the given entity by persisting it in the database and sets the id value which is generated by the DB.
107+
*
108+
* @param entity the entity to be saved
109+
* @throws RuntimeException if the method is not implemented
110+
*/
111+
@Override
112+
public void save(Object entity) {
113+
throw new UnsupportedOperationException("Method save() is not implemented yet"); // todo: implement this method and remove the exception
114+
}
115+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.bobocode.bibernate.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.FIELD)
10+
public @interface Column {
11+
String value();
12+
13+
boolean updatable() default true;
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.bobocode.bibernate.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.TYPE)
10+
public @interface Entity {
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.bobocode.bibernate.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.FIELD)
10+
public @interface Id {
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.bobocode.bibernate.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.TYPE)
10+
public @interface Table {
11+
String value();
12+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.bobocode.entity;
2+
3+
import com.bobocode.bibernate.annotation.Column;
4+
import com.bobocode.bibernate.annotation.Entity;
5+
import com.bobocode.bibernate.annotation.Id;
6+
import com.bobocode.bibernate.annotation.Table;
7+
import lombok.Data;
8+
import lombok.NoArgsConstructor;
9+
10+
import java.time.LocalDateTime;
11+
12+
@Entity
13+
@Table("participants")
14+
@Data
15+
@NoArgsConstructor
16+
public class Participant {
17+
@Id
18+
private Integer id;
19+
20+
private String firstName;
21+
22+
private String lastName;
23+
24+
private String city;
25+
26+
private String company;
27+
28+
private String position;
29+
30+
private Integer yearsOfExperience;
31+
32+
@Column(value = "created_at")
33+
private LocalDateTime createdAt;
34+
}

0 commit comments

Comments
 (0)