feature: Implement Serialized Entity Pattern (#2150)

* Add files:
- App.java
- Country.java
- CountryDao.java
- CountrySchemaSql.java
- CountryTest.java
- README.md
- serialize-entity.urm.puml
- pom.xml (inherit from parent pom.xml)
Update files:
- pom.xml (parent pom.xml, add 1 module called serialized-entity)

* Fix duplicated error in pom.xml

* Update:
- pom.xml (module servant, changed by error, now fixed)
- pom.xml (module serialized-entity)

* Update:
- pom.xml (changed, artifactId)

* Update:
- pom.xml

Add:
AppTest.java

* Resolved changes required by the reviewer

Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
This commit is contained in:
JurenXu
2022-11-13 11:43:03 +04:00
committed by GitHub
parent 953f9a4e0c
commit b103e44ecd
12 changed files with 816 additions and 0 deletions
@@ -0,0 +1,128 @@
/*
* 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.serializedentity;
import java.io.IOException;
import java.sql.SQLException;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.h2.jdbcx.JdbcDataSource;
/**
* Serialized Entity Pattern.
*
* <p> Serialized Entity Pattern allow us to easily persist Java objects to the database. It uses Serializable interface
* and DAO pattern. Serialized Entity Pattern will first use Serializable to convert a Java object into a set of bytes,
* then it will using DAO pattern to store this set of bytes as BLOB to database.</p>
*
* <p> In this example, we first initialize two Java objects (Country) "China" and "UnitedArabEmirates", then we
* initialize "serializedChina" with "China" object and "serializedUnitedArabEmirates" with "UnitedArabEmirates",
* then we use method "serializedChina.insertCountry()" and "serializedUnitedArabEmirates.insertCountry()" to serialize
* "China" and "UnitedArabEmirates" and persist them to database.
* Last, with "serializedChina.selectCountry()" and "serializedUnitedArabEmirates.selectCountry()" we could read "China"
* and "UnitedArabEmirates" from database as sets of bytes, then deserialize them back to Java object (Country). </p>
*
*/
@Slf4j
public class App {
private static final String DB_URL = "jdbc:h2:~/test";
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);
// Initializing Country Object China
final var China = new Country(
86,
"China",
"Asia",
"Chinese"
);
// Initializing Country Object UnitedArabEmirates
final var UnitedArabEmirates = new Country(
971,
"United Arab Emirates",
"Asia",
"Arabic"
);
// Initializing CountrySchemaSql Object with parameter "China" and "dataSource"
final var serializedChina = new CountrySchemaSql(China, dataSource);
// Initializing CountrySchemaSql Object with parameter "UnitedArabEmirates" and "dataSource"
final var serializedUnitedArabEmirates = new CountrySchemaSql(UnitedArabEmirates, dataSource);
/*
By using CountrySchemaSql.insertCountry() method, the private (Country) type variable within Object
CountrySchemaSql will be serialized to a set of bytes and persist to database.
For more details of CountrySchemaSql.insertCountry() method please refer to CountrySchemaSql.java file
*/
serializedChina.insertCountry();
serializedUnitedArabEmirates.insertCountry();
/*
By using CountrySchemaSql.selectCountry() method, CountrySchemaSql object will read the sets of bytes from database
and deserialize it to Country object.
For more details of CountrySchemaSql.selectCountry() method please refer to CountrySchemaSql.java file
*/
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;
}
}
@@ -0,0 +1,47 @@
/*
* 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.serializedentity;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* A Country POJO taht represents the data that will serialize and store in database.
*/
@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;
}
@@ -0,0 +1,45 @@
/*
* 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.
*/
/**
* In an application the Data Access Object (DAO) is a part of Data access layer. It is an object
* that provides an interface to some type of persistence mechanism. By mapping application calls to
* the persistence layer, DAO provides some specific data operations without exposing details of the
* database. This isolation supports the Single responsibility principle. It separates what data
* accesses the application needs, in terms of domain-specific objects and data types (the public
* interface of the DAO), from how these needs can be satisfied with a specific DBMS, database
* schema, etc.
*
* <p>Any change in the way data is stored and retrieved will not change the client code as the
* client will be using interface and need not worry about exact source.
*
* @see InMemoryCustomerDao
* @see DbCustomerDao
*/
package com.iluwatar.serializedentity;
import java.io.IOException;
public interface CountryDao {
int insertCountry() throws IOException;
int selectCountry() throws IOException, ClassNotFoundException;
}
@@ -0,0 +1,122 @@
/*
* 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.serializedentity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Blob;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
/**
* Country Schema SQL Class.
*/
@Slf4j
public class CountrySchemaSql implements CountryDao {
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.
*/
@Override
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.
*/
@Override
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;
}
}