diff --git a/lesson-demo/pom.xml b/lesson-demo/pom.xml index 80b50add..9e9a991a 100644 --- a/lesson-demo/pom.xml +++ b/lesson-demo/pom.xml @@ -10,5 +10,18 @@ 4.0.0 lesson-demo + + + + org.postgresql + postgresql + 42.7.3 + + + com.google.guava + guava + 33.2.0-jre + + \ No newline at end of file diff --git a/lesson-demo/src/main/java/com/bobocode/DemoApp.java b/lesson-demo/src/main/java/com/bobocode/DemoApp.java index 21d5205b..4405f9cb 100644 --- a/lesson-demo/src/main/java/com/bobocode/DemoApp.java +++ b/lesson-demo/src/main/java/com/bobocode/DemoApp.java @@ -1,7 +1,14 @@ package com.bobocode; +import com.bobocode.bibernate.OrmImpl; +import com.bobocode.entity.Quote; + public class DemoApp { public static void main(String[] args) { - + var orm = new OrmImpl("jdbc:postgresql://0.tcp.eu.ngrok.io:13243/postgres", "bobouser", "bobopass"); + + var quote = orm.findById(Quote.class, 1); + System.out.println(quote); } + } diff --git a/lesson-demo/src/main/java/com/bobocode/bibernate/Orm.java b/lesson-demo/src/main/java/com/bobocode/bibernate/Orm.java new file mode 100644 index 00000000..efaa7509 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/bibernate/Orm.java @@ -0,0 +1,7 @@ +package com.bobocode.bibernate; + +public interface Orm { + T findById(Class entityType, Object id); + + void save(Object entity); +} diff --git a/lesson-demo/src/main/java/com/bobocode/bibernate/OrmImpl.java b/lesson-demo/src/main/java/com/bobocode/bibernate/OrmImpl.java new file mode 100644 index 00000000..26a98874 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/bibernate/OrmImpl.java @@ -0,0 +1,115 @@ +package com.bobocode.bibernate; + +import com.bobocode.bibernate.annotation.Column; +import com.bobocode.bibernate.annotation.Entity; +import com.bobocode.bibernate.annotation.Id; +import com.bobocode.bibernate.annotation.Table; +import com.google.common.base.CaseFormat; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.postgresql.ds.PGSimpleDataSource; + +import javax.sql.DataSource; +import java.lang.reflect.Field; +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Optional; + +@RequiredArgsConstructor +public class OrmImpl implements Orm { + public static final String SELECT_FROM_TABLE_BY_COLUMN = "select * from %s where %s = ?"; + private final DataSource dataSource; + + public OrmImpl(String jdbcUrl, String username, String password) { + var pgSimpleDataSource = new PGSimpleDataSource(); + pgSimpleDataSource.setURL(jdbcUrl); + pgSimpleDataSource.setUser(username); + pgSimpleDataSource.setPassword(password); + this.dataSource = pgSimpleDataSource; + } + + @Override + @SneakyThrows + public T findById(Class entityType, Object id) { + verifyEntity(entityType); + try (var connection = dataSource.getConnection()) { + var selectSql = buildSqlSelectById(entityType); + System.out.println("SQL: " + selectSql); + try (var selectStatement = connection.prepareStatement(selectSql)) { + selectStatement.setObject(1, id); + var rs = selectStatement.executeQuery(); + if (rs.next()) { + return createEntityFromResultSet(entityType, rs); + } else { + throw new RuntimeException("Entity not found by id =" + id); + } + } + + } + } + + private void verifyEntity(Class entityType) { + if (!entityType.isAnnotationPresent(Entity.class)) { + throw new RuntimeException(entityType.getSimpleName() + " is not an @Entity"); + } + } + + private String buildSqlSelectById(Class entityType) { + var tableName = resolveTableName(entityType); + var idColumnName = resolveIdColumnName(entityType); + return SELECT_FROM_TABLE_BY_COLUMN.formatted(tableName, idColumnName); + } + + private String resolveTableName(Class entityType) { + return Optional.ofNullable(entityType.getAnnotation(Table.class)) + .map(Table::value) + .orElseGet(() -> underscore(entityType.getSimpleName())); + } + + private String resolveIdColumnName(Class entityType) { + return Arrays.stream(entityType.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Id.class)) + .findAny() + .map(this::resolveColumnName) + .orElseThrow(() -> new RuntimeException("Entity " + entityType.getSimpleName() + " must have an @Id")); + } + + private String resolveColumnName(Field field) { + return Optional.ofNullable(field.getAnnotation(Column.class)) + .map(Column::value) + .orElseGet(() -> underscore(field.getName())); + } + + private String underscore(String value) { + return CaseFormat.LOWER_CAMEL + .converterTo(CaseFormat.LOWER_UNDERSCORE) + .convert(value); + } + + @SneakyThrows + private T createEntityFromResultSet(Class entityType, ResultSet rs) { + var entity = entityType.getConstructor().newInstance(); + for (var field : entityType.getDeclaredFields()) { + var columnName = resolveColumnName(field); + var fieldValue = rs.getObject(columnName); + if (fieldValue instanceof Timestamp timestamp) { + fieldValue = timestamp.toLocalDateTime(); + } + field.setAccessible(true); + field.set(entity, fieldValue); + } + return entity; + } + + /** + * Saves the given entity by persisting it in the database and sets the id value which is generated by the DB. + * + * @param entity the entity to be saved + * @throws RuntimeException if the method is not implemented + */ + @Override + public void save(Object entity) { + throw new UnsupportedOperationException("Method save() is not implemented yet"); // todo: implement this method and remove the exception + } +} diff --git a/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Column.java b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Column.java new file mode 100644 index 00000000..58d818c5 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Column.java @@ -0,0 +1,14 @@ +package com.bobocode.bibernate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Column { + String value(); + + boolean updatable() default true; +} diff --git a/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Entity.java b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Entity.java new file mode 100644 index 00000000..00fb224f --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Entity.java @@ -0,0 +1,11 @@ +package com.bobocode.bibernate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Entity { +} diff --git a/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Id.java b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Id.java new file mode 100644 index 00000000..962586e6 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Id.java @@ -0,0 +1,11 @@ +package com.bobocode.bibernate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Id { +} diff --git a/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Table.java b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Table.java new file mode 100644 index 00000000..e6ded160 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/bibernate/annotation/Table.java @@ -0,0 +1,12 @@ +package com.bobocode.bibernate.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Table { + String value(); +} diff --git a/lesson-demo/src/main/java/com/bobocode/entity/Participant.java b/lesson-demo/src/main/java/com/bobocode/entity/Participant.java new file mode 100644 index 00000000..872a2228 --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/entity/Participant.java @@ -0,0 +1,32 @@ +package com.bobocode.entity; + +import com.bobocode.bibernate.annotation.Entity; +import com.bobocode.bibernate.annotation.Id; +import com.bobocode.bibernate.annotation.Table; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table("participants") +@Data +@NoArgsConstructor +public class Participant { + @Id + private Integer id; + + private String firstName; + + private String lastName; + + private String city; + + private String company; + + private String position; + + private Integer yearsOfExperience; + + private LocalDateTime createdAt; +} \ No newline at end of file diff --git a/lesson-demo/src/main/java/com/bobocode/entity/Quote.java b/lesson-demo/src/main/java/com/bobocode/entity/Quote.java new file mode 100644 index 00000000..b208feae --- /dev/null +++ b/lesson-demo/src/main/java/com/bobocode/entity/Quote.java @@ -0,0 +1,25 @@ +package com.bobocode.entity; + +import com.bobocode.bibernate.annotation.Column; +import com.bobocode.bibernate.annotation.Entity; +import com.bobocode.bibernate.annotation.Id; +import com.bobocode.bibernate.annotation.Table; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table("quotes") +@Data +@NoArgsConstructor +public class Quote { + @Id + private Integer id; + + private String body; + + private String author; + + private LocalDateTime createdAt; +} \ No newline at end of file