Skip to content

Implement an active record pattern (#79) #2863

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fedbb99
provide an initial module and stubs (#79)
sergejsvisockis Mar 28, 2024
8940396
format the code as per the google checkstyle (#79)
sergejsvisockis Mar 28, 2024
33aecdc
provide an initial implementation - not tested (#79)
sergejsvisockis Mar 28, 2024
f4e2520
update README.md (#79)
sergejsvisockis Mar 28, 2024
584a8ca
update an exception message (#79)
sergejsvisockis Mar 28, 2024
aaf203c
add javadoc (#79)
sergejsvisockis Mar 28, 2024
6cfbf80
update README.md (#79)
sergejsvisockis Mar 28, 2024
a8cdba2
draft more comprehensive explanation (#79)
sergejsvisockis Mar 28, 2024
fe908cc
no comment (#79)
sergejsvisockis Mar 29, 2024
29ed0f9
extend the logic of the RecordBase + manual test (#79)
sergejsvisockis Mar 29, 2024
99d5a5a
fix all the checkstyle violations (#79)
sergejsvisockis Mar 29, 2024
ad80931
work on exceptions (#79)
sergejsvisockis Mar 29, 2024
1b8217c
make order an active record class as well + javadoc (#79)
sergejsvisockis Mar 29, 2024
53304a5
fix POM (#79)
sergejsvisockis Mar 29, 2024
49c3f63
polish + exception (#79)
sergejsvisockis Mar 30, 2024
670c1b7
implement a simple save method (#79)
sergejsvisockis Mar 30, 2024
df5dfa9
developers -> engineers (#79)
sergejsvisockis Mar 30, 2024
1a8d330
add TODO (#79)
sergejsvisockis Mar 31, 2024
318d34e
fix type (#79)
sergejsvisockis Mar 31, 2024
c314241
add plantUML diagram (#79)
sergejsvisockis Apr 1, 2024
ed07c7a
generate an insert query from the class fields
sergejsvisockis Apr 1, 2024
e90122e
add TODO
sergejsvisockis Apr 1, 2024
101fa17
draft unit tests
sergejsvisockis Apr 1, 2024
a319a35
fix violations
sergejsvisockis Apr 6, 2024
57a92d9
add TODO
sergejsvisockis Apr 6, 2024
eb1c044
vix violation
sergejsvisockis Apr 6, 2024
ee6716d
make protected
sergejsvisockis Apr 6, 2024
6e1af71
remove TODO
sergejsvisockis Apr 7, 2024
4b8551c
push non-working solution
sergejsvisockis Apr 11, 2024
52f0286
refer to read methods from the static context
sergejsvisockis Apr 16, 2024
1dd0ea3
add more unit tests
sergejsvisockis Apr 16, 2024
ff8636f
javadoc
sergejsvisockis Apr 16, 2024
163052d
move selection query into the Query class
sergejsvisockis Apr 20, 2024
385cd4b
add copyright
sergejsvisockis Apr 20, 2024
795ccc7
fix typo & make non-static
sergejsvisockis Apr 21, 2024
db61c18
where -> withKey
sergejsvisockis Apr 21, 2024
98b82c7
wrap findById into the Optional and introduce the deletion by ID meth…
sergejsvisockis May 12, 2024
c35f2e1
provide a proper test coverage for the Query API
sergejsvisockis May 12, 2024
035b1af
improve an insertion query
sergejsvisockis May 12, 2024
7de7749
Merge branch 'iluwatar:master' into active_record_pattern
sergejsvisockis May 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions active-record/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: Active Record
category: Architectural
language: en
tag:
- Data access
---

## Intent

The Active Record pattern is a design pattern that integrates data access logic directly into the
domain model, typically within the model classes themselves. This means that each domain object is
responsible for its own persistence, including tasks such as database querying, saving, updating,
and deleting records.

This pattern is particularly useful in scenarios where simplicity and rapid development are
prioritized, as it allows engineers to quickly implement data access functionality with minimal
effort. By encapsulating persistence logic within the domain objects, the Active Record pattern
promotes a straightforward and intuitive approach to working with data, making it easier to
understand and maintain codebases.

## Explanation

## Class diagram

## Tutorials

* [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

## Related patterns

## Credits
47 changes: 47 additions & 0 deletions active-record/etc/active-record.urm.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@startuml
package com.iluwatar.activerecord {
abstract class RecordBase<T extends RecordBase<?>> {
- dataSource : DataSource
- clazz : Class<T>
+ setDataSource(DataSource) : void
# getConnection() : Connection
# abstract getTableName() : String
# abstract setFieldsFromResultSet(ResultSet) : void
# abstract setPreparedStatementParams(PreparedStatement) : void
+ findAll() : List<T>
+ findById(Long id): T
+ save() : void
+ delete() : void
- constructFindByIdQuery() : String
- constructFindAllQuery() : String
- getDeclaredClassInstance() : T
}

class Customer extends RecordBase {
- id : Long
- customerNumber : String
- firstName : String
- lastName : String
- List<Order> orders
+ getId() : Long
+ setId(Long id) : void
+ getCustomerNumber() : String
+ setCustomerNumber(String customerNumber) : void
+ getfirstName() : String
+ setFirstName(String firstName) : void
+ getLastName() : String
+ setLastName(String lastName) : void
+ findByNumber(String customerNumber) : Customer
+ addOrder(Order order) : void
}

class Order extends RecordBase {
- id : Long
- orderNumber : String
+ getId() : Long
+ setId(Long id) : void
+ getOrderNumber() : String
+ setOrderNumber(String orderNumber) : String
}
}
@enduml
56 changes: 56 additions & 0 deletions active-record/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>

<artifactId>active-record</artifactId>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.iluwatar.activerecord.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
91 changes: 91 additions & 0 deletions active-record/src/main/java/com/iluwatar/activerecord/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.activerecord;

import static com.iluwatar.activerecord.SchemaConstants.CREATE_SCHEMA_SQL;

import com.iluwatar.activerecord.base.RecordBase;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.h2.jdbcx.JdbcDataSource;

/**
* The main application for the manual testing purposes.
*/
@Slf4j
public class App {

private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";

/**
* Java main method to execute all the logic out there.
*
* @param args arguments.
* @throws Exception Any sort of exception that has to be picked up by the JVM.
*/
public static void main(final String[] args) throws Exception {
final DataSource 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(Customer.class);

LOGGER.info("The customer data by ID={}", customer.findById(1L, Customer.class));

LOGGER.info("find all the customers={}", customer.findAll(Customer.class));
}

private static void createSchema(DataSource dataSource) throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute(CREATE_SCHEMA_SQL);
}
}

private static DataSource createDataSource() {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
return dataSource;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.activerecord;

import com.iluwatar.activerecord.base.RecordBase;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
* The customer domain model.
*/
@Getter
@Setter
@ToString
public class Customer extends RecordBase {

private Long id;
private String customerNumber;
private String firstName;
private String lastName;
private List<Order> orders;

public Customer findByNumber(String customerNumber) {
// TODO
return null;
}

public void addOrder(Order order) {
orders.add(order);
}

@Override
protected String getTableName() {
return "customer";
}

@Override
protected void setFieldsFromResultSet(ResultSet rs) throws SQLException {
this.id = rs.getLong("id");
this.customerNumber = rs.getString("customerNumber");
this.firstName = rs.getString("firstName");
this.lastName = rs.getString("lastName");
}

@Override
protected void setPreparedStatementParams(PreparedStatement pstmt) throws SQLException {
pstmt.setLong(1, id);
pstmt.setString(2, customerNumber);
pstmt.setString(3, firstName);
pstmt.setString(4, lastName);
}
}
65 changes: 65 additions & 0 deletions active-record/src/main/java/com/iluwatar/activerecord/Order.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.activerecord;

import com.iluwatar.activerecord.base.RecordBase;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* An order domain model.
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Order extends RecordBase {

private Long id;
private String orderNumber;

@Override
protected String getTableName() {
return "order";
}

@Override
protected void setFieldsFromResultSet(ResultSet rs) throws SQLException {
this.id = rs.getLong("id");
this.orderNumber = rs.getString("orderNumber");
}

@Override
protected void setPreparedStatementParams(PreparedStatement pstmt)
throws SQLException {

}

}
Loading
Loading