diff --git a/active-record/README.md b/active-record/README.md index 2deccfc96761..14627e50b01e 100644 --- a/active-record/README.md +++ b/active-record/README.md @@ -25,8 +25,8 @@ understand and maintain codebases. ## Tutorials -* [Panache – Active Record Pattern](https://thorben-janssen.com/panache-active-record-pattern/) -* [Active Record pattern in Java](https://objsql.hashnode.dev/active-record-pattern-in-java) +* [Active Record](https://www.martinfowler.com/eaaCatalog/activeRecord.html/) +* [Overview of the Active Record Pattern](https://blog.savetchuk.com/overview-of-the-active-record-pattern) ## Consequences diff --git a/active-record/src/main/java/com/iluwatar/activerecord/App.java b/active-record/src/main/java/com/iluwatar/activerecord/App.java new file mode 100644 index 000000000000..b6641875ee71 --- /dev/null +++ b/active-record/src/main/java/com/iluwatar/activerecord/App.java @@ -0,0 +1,72 @@ +package com.iluwatar.activerecord; + +import java.sql.SQLException; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import org.h2.jdbcx.JdbcDataSource; + +@Slf4j +public class App { + + private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1"; + + private static final String CREATE_SCHEMA_SQL = "CREATE TABLE customer\n" + + "(\n" + + " id BIGINT NOT NULL,\n" + + " customer_number VARCHAR(15) NOT NULL,\n" + + " first_name VARCHAR(45) NOT NULL,\n" + + " last_name VARCHAR(45) NOT NULL,\n" + + " CONSTRAINT customer_pkey PRIMARY KEY (id)\n" + + ");\n" + + "\n" + + "CREATE TABLE \"order\"\n" + + "(\n" + + " id BIGINT NOT NULL,\n" + + " order_number VARCHAR(15) NOT NULL,\n" + + " customer_id BIGINT NOT NULL,\n" + + " CONSTRAINT order_pkey PRIMARY KEY (id),\n" + + " CONSTRAINT customer_id_fk FOREIGN KEY (customer_id) REFERENCES customer (id)\n" + + ")"; + + + public static void main(final String[] args) throws Exception { + final var dataSource = createDataSource(); + createSchema(dataSource); + RecordBase.setDataSource(dataSource); + executeOperation(); + } + + private static void executeOperation() { + LOGGER.info("saving the customer data..."); + Customer customer = new Customer(); + customer.setId(1L); + customer.setCustomerNumber("C123"); + customer.setFirstName("John"); + customer.setLastName("Smith"); + + Order order = new Order(); + order.setId(1L); + order.setOrderNumber("O123"); + +// customer.addOrder(order); + customer.save(); + + LOGGER.info("The customer data by ID={}", customer.findById(1L)); + + LOGGER.info("find all the customers={}", customer.findAll()); + } + + private static void createSchema(DataSource dataSource) throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(CREATE_SCHEMA_SQL); + } + } + + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + return dataSource; + } + +} diff --git a/active-record/src/main/java/com/iluwatar/activerecord/Customer.java b/active-record/src/main/java/com/iluwatar/activerecord/Customer.java index fab6b2a7fd5e..45fa7124cfe4 100644 --- a/active-record/src/main/java/com/iluwatar/activerecord/Customer.java +++ b/active-record/src/main/java/com/iluwatar/activerecord/Customer.java @@ -1,14 +1,17 @@ package com.iluwatar.activerecord; +import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; -import javax.sql.DataSource; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter -public class Customer extends RecordBase { +@ToString +public class Customer extends RecordBase { private Long id; private String customerNumber; @@ -16,14 +19,13 @@ public class Customer extends RecordBase { private String lastName; private List orders; - public Customer(DataSource dataSource) { - super(dataSource); - } - public Customer findByNumber(String customerNumber) { // TODO return null; -// return new Customer(); + } + + public void addOrder(Order order) { + orders.add(order); } @Override @@ -32,7 +34,15 @@ protected String getTableName() { } @Override - protected void setFieldsFromResultSet(ResultSet rs) { + protected void setFieldsFromResultSet(ResultSet rs) throws SQLException { + this.id = rs.getLong("id"); + this.customerNumber = rs.getString("customer_number"); + this.firstName = rs.getString("first_name"); + this.lastName = rs.getString("last_name"); + } + + @Override + protected void setPreparedStatementParams(PreparedStatement pstmt) throws SQLException { } } diff --git a/active-record/src/main/java/com/iluwatar/activerecord/RecordBase.java b/active-record/src/main/java/com/iluwatar/activerecord/RecordBase.java index 668c4f2a0c00..dd6436c7ebdf 100644 --- a/active-record/src/main/java/com/iluwatar/activerecord/RecordBase.java +++ b/active-record/src/main/java/com/iluwatar/activerecord/RecordBase.java @@ -9,11 +9,16 @@ import java.util.List; import javax.sql.DataSource; import lombok.RequiredArgsConstructor; +import lombok.Setter; @RequiredArgsConstructor -public abstract class RecordBase { +public abstract class RecordBase> { - private final DataSource dataSource; + @Setter + private static DataSource dataSource; + + @SuppressWarnings({"unchecked"}) + private final Class clazz = (Class) getClass(); protected Connection getConnection() throws SQLException { return dataSource.getConnection(); @@ -26,22 +31,22 @@ protected Connection getConnection() throws SQLException { */ protected abstract String getTableName(); - protected abstract void setFieldsFromResultSet(ResultSet rs); + protected abstract void setFieldsFromResultSet(ResultSet rs) throws SQLException; + + protected abstract void setPreparedStatementParams(PreparedStatement pstmt) throws SQLException; /** * Find all the records for a corresponding domain model. * - * @param clazz domain model class. - * @param domain model type. * @return all the domain model related records. */ - public List findAll(Class clazz) { + public List findAll() { List recordList = new ArrayList<>(); try (Connection conn = getConnection(); - PreparedStatement pstmt = conn.prepareStatement(constructGetByIdQuery(clazz))) { + PreparedStatement pstmt = conn.prepareStatement(constructFindAllQuery())) { try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { - T record = getDeclaredClassInstance(clazz); + T record = getDeclaredClassInstance(); record.setFieldsFromResultSet(rs); recordList.add(record); } @@ -57,26 +62,24 @@ public List findAll(Class clazz) { /** * Find a domain model by its ID. * - * @param id domain model identifier. - * @param clazz domain model class. - * @param domain model type. + * @param id domain model identifier. * @return the domain model. */ - public T findById(Long id, Class clazz) { + public T findById(Long id) { try (Connection conn = getConnection(); - PreparedStatement pstmt = conn.prepareStatement(constructGetByIdQuery(clazz))) { + PreparedStatement pstmt = conn.prepareStatement(constructFindByIdQuery())) { pstmt.setLong(1, id); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { - T record = getDeclaredClassInstance(clazz); + T record = getDeclaredClassInstance(); record.setFieldsFromResultSet(rs); return record; } - return getDeclaredClassInstance(clazz); + return getDeclaredClassInstance(); } } catch (SQLException e) { throw new RuntimeException( - "Unable to fine a record for the following domain model : " + clazz.getName() + " by id=" + "Unable to find a record for the following domain model : " + clazz.getName() + " by id=" + id + " due to the data persistence error", e); } @@ -86,15 +89,32 @@ public T findById(Long id, Class clazz) { * Save the record. */ public void save() { - // TODO + try (Connection connection = getConnection(); + // TODO + PreparedStatement pstmt = connection.prepareStatement(null, + PreparedStatement.RETURN_GENERATED_KEYS)) { + + setPreparedStatementParams(pstmt); + pstmt.executeUpdate(); + + } catch (SQLException e) { + throw new RuntimeException( + "Unable to save the record for the following domain model : " + clazz.getName() + + " due to the data persistence error", e); + } + } + + + private String constructFindAllQuery() { + return "SELECT * FROM " + getDeclaredClassInstance().getTableName(); } - private String constructGetByIdQuery(Class clazz) { - return "SELECT * FROM " + getDeclaredClassInstance(clazz).getTableName() + private String constructFindByIdQuery() { + return "SELECT * FROM " + getDeclaredClassInstance().getTableName() + " WHERE id = ?"; } - private T getDeclaredClassInstance(Class clazz) { + private T getDeclaredClassInstance() { try { return clazz.getDeclaredConstructor().newInstance(); } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException |