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