Files
java-design-patterns/serialized-entity/README.md
T
Jakub Klimek 0f39267a19 refactor: #2366 Change h2 database usage to in mem (#2776)
* fix: Change h2 database usage to in mem (#2366)

* #2366 Add delay option
2024-01-27 13:07:10 +02:00

222 lines
7.1 KiB
Markdown

---
title: Serialized Entity Pattern
categories: Architectural
language: en
tag:
- Data access
---
## Intent
To easily persist Java objects to the database.
## Explanation
Java serialization allow us to convert the object to a set of bytes. We can store these bytes into database as
BLOB(binary long objects) and read them at any time and reconstruct them into Java objects.
**Programmatic Example**
Walking through our customers example, here's the basic `Customer` entity.
```java
@Getter
@Setter
@EqualsAndHashCode
@ToString
@AllArgsConstructor
public class Country implements Serializable {
private int code;
private String name;
private String continents;
private String language;
public static final long serialVersionUID = 7149851;
// Constructor ->
// getters and setters ->
}
```
Here is `CountrySchemaSql`, this class have method allow us to serialize `Country` object and insert it into the
database, also have a method that read serialized data from the database and deserialize it to `Country` object.
```java
@Slf4j
public class CountrySchemaSql {
public static final String CREATE_SCHEMA_SQL = "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)";
public static final String DELETE_SCHEMA_SQL = "DROP TABLE WORLD IF EXISTS";
private Country country;
private DataSource dataSource;
/**
* Public constructor.
*
* @param dataSource datasource
* @param country country
*/
public CountrySchemaSql(Country country, DataSource dataSource) {
this.country = new Country(
country.getCode(),
country.getName(),
country.getContinents(),
country.getLanguage()
);
this.dataSource = dataSource;
}
/**
* This method will serialize a Country object and store it to database.
* @return int type, if successfully insert a serialized object to database then return country code, else return -1.
* @throws IOException if any.
*/
public int insertCountry() throws IOException {
var sql = "INSERT INTO WORLD (ID, COUNTRY) VALUES (?, ?)";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(baos)) {
oss.writeObject(country);
oss.flush();
preparedStatement.setInt(1, country.getCode());
preparedStatement.setBlob(2, new ByteArrayInputStream(baos.toByteArray()));
preparedStatement.execute();
return country.getCode();
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
return -1;
}
/**
* This method will select a data item from database and deserialize it.
* @return int type, if successfully select and deserialized object from database then return country code,
* else return -1.
* @throws IOException if any.
* @throws ClassNotFoundException if any.
*/
public int selectCountry() throws IOException, ClassNotFoundException {
var sql = "SELECT ID, COUNTRY FROM WORLD WHERE ID = ?";
try (var connection = dataSource.getConnection();
var preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setInt(1, country.getCode());
try (ResultSet rs = preparedStatement.executeQuery()) {
if (rs.next()) {
Blob countryBlob = rs.getBlob("country");
ByteArrayInputStream baos = new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length()));
ObjectInputStream ois = new ObjectInputStream(baos);
country = (Country) ois.readObject();
LOGGER.info("Country: " + country);
}
return rs.getInt("id");
}
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
return -1;
}
}
```
This `App.java` will first delete a World table from the h2 database(if there is one) and create a new table called
`WORLD` to ensure we have a table we want.
It will then create two `Country` objects called `China` and `UnitedArabEmirates`, then create two `CountrySchemaSql`
objects and use objects `China` and `UnitedArabEmirates` as arguments.
Finally, call `SerializedChina.insertCountry()` and `serializedUnitedArabEmirates.insertCountry()` to serialize and
store them to database, and call `SerializedChina.selectCountry()` and `serializedUnitedArabEmirates.selectCountry()`
methods to read and deserialize data items to `Country` objects.
```java
@Slf4j
public class App {
private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
private App() {
}
/**
* Program entry point.
* @param args command line args.
* @throws IOException if any
* @throws ClassNotFoundException if any
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
final var dataSource = createDataSource();
deleteSchema(dataSource);
createSchema(dataSource);
final var China = new Country(
86,
"China",
"Asia",
"Chinese"
);
final var UnitedArabEmirates = new Country(
971,
"United Arab Emirates",
"Asia",
"Arabic"
);
final var serializedChina = new CountrySchemaSql(China, dataSource);
final var serializedUnitedArabEmirates = new CountrySchemaSql(UnitedArabEmirates, dataSource);
serializedChina.insertCountry();
serializedUnitedArabEmirates.insertCountry();
serializedChina.selectCountry();
serializedUnitedArabEmirates.selectCountry();
}
private static void deleteSchema(DataSource dataSource) {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CountrySchemaSql.DELETE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
}
private static void createSchema(DataSource dataSource) {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CountrySchemaSql.CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
return dataSource;
}
}
```
## Class diagram
![alt_text](./etc/class_diagram.urm.png "Serialized Entity")
## Applicability
Use the Serialized Entity pattern when
* You want to easily persist Java objects to the database.
## Related patterns
[Data Access Object](https://github.com/iluwatar/java-design-patterns/tree/master/dao)
## Credits
* [J2EE Design Patterns by William Crawford, Jonathan Kaplan](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re21.html)
* [komminen](https://github.com/komminen/java-design-patterns) (His attempts of serialized-entity inspired me and learned a lot from his code)