From b103e44ecd61c24f9eb0550fe01d6ca2419b6abd Mon Sep 17 00:00:00 2001 From: JurenXu <101047547+JurenXu@users.noreply.github.com> Date: Sun, 13 Nov 2022 11:43:03 +0400 Subject: [PATCH] feature: Implement Serialized Entity Pattern (#2150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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ä --- pom.xml | 1 + serialized-entity/README.md | 221 ++++++++++++++++++ serialized-entity/etc/class_diagram.urm.png | Bin 0 -> 76171 bytes .../etc/serialize-entity.urm.puml | 56 +++++ serialized-entity/pom.xml | 71 ++++++ .../com/iluwatar/serializedentity/App.java | 128 ++++++++++ .../iluwatar/serializedentity/Country.java | 47 ++++ .../iluwatar/serializedentity/CountryDao.java | 45 ++++ .../serializedentity/CountrySchemaSql.java | 122 ++++++++++ .../iluwatar/serializedentity/AppTest.java | 23 ++ .../serializedentity/CountryTest.java | 101 ++++++++ servant/pom.xml | 1 + 12 files changed, 816 insertions(+) create mode 100644 serialized-entity/README.md create mode 100644 serialized-entity/etc/class_diagram.urm.png create mode 100644 serialized-entity/etc/serialize-entity.urm.puml create mode 100644 serialized-entity/pom.xml create mode 100644 serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java create mode 100644 serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java create mode 100644 serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java create mode 100644 serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java create mode 100644 serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java create mode 100644 serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java diff --git a/pom.xml b/pom.xml index 7ed97bcae..c0ead428c 100644 --- a/pom.xml +++ b/pom.xml @@ -215,6 +215,7 @@ composite-view metadata-mapping service-to-worker + serialized-entity identity-map diff --git a/serialized-entity/README.md b/serialized-entity/README.md new file mode 100644 index 000000000..a0a3853f9 --- /dev/null +++ b/serialized-entity/README.md @@ -0,0 +1,221 @@ +--- +title: Serialized Entity Pattern +categories: Architectural +language: en +tags: + - 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:~/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); + + 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) diff --git a/serialized-entity/etc/class_diagram.urm.png b/serialized-entity/etc/class_diagram.urm.png new file mode 100644 index 0000000000000000000000000000000000000000..fb8e7a73428d821ea3cc705cd4d3c0eb09fd7522 GIT binary patch literal 76171 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%0Db63 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 bM^mB~jZ)&Hen{^m-!RbGuAQy5=h%M%vLUZ& literal 0 HcmV?d00001 diff --git a/serialized-entity/etc/serialize-entity.urm.puml b/serialized-entity/etc/serialize-entity.urm.puml new file mode 100644 index 000000000..7bf89dbb9 --- /dev/null +++ b/serialized-entity/etc/serialize-entity.urm.puml @@ -0,0 +1,56 @@ +@startuml +package com.iluwatar.serializedentity { + class App { + - DB_URL : String {static} + - LOGGER : Logger {static} + + App() + - createDataSource() : DataSource {static} + - createSchema(dataSource : DataSource) {static} + - deleteSchema(dataSource : DataSource) {static} + + main(args : String[]) {static} + } + + class Country { + - code : int + - name : String + - continents : String + - language : String + + serialVersionUID : final Long {static} + ~Country(int, String, String, String) + + getCode() : int + + getName() : String + + getContinents() : String + + getLanguage() : String + + setCode(code : int) + + setName(name : String) + + setContinents(continents : String) + + setLanguage(language : String) + + equals(that: Object) : boolean + + hashCode() : int + + toString() : String + } + + interface CountryDao { + + insertCountry : int {abstract} + + selectCountry : int {abstract} + } + + class CountrySchemaSql { + - CREATE_SCHEMA_SQL : String {static} + - DELETE_SCHEMA_SQL : String {static} + - LOGGER : Logger {static} + - dataSource : DataSource + - country : Country + ~ CountrySchemaSql(country : Country, dataSource : DataSource) + + insertCountry() + + selectCountry() + } + diamond h2db +} + +App --> "Initialize Country objects" Country +App --> "Initialize CountrySchemaSql" CountrySchemaSql +CountrySchemaSql --> "implements" CountryDao +Country --> "Stored in database" h2db +CountrySchemaSql --> "Perform INSERT and SELECT operation after serialize and deserialize Country object" h2db +@enduml \ No newline at end of file diff --git a/serialized-entity/pom.xml b/serialized-entity/pom.xml new file mode 100644 index 000000000..aea122f5a --- /dev/null +++ b/serialized-entity/pom.xml @@ -0,0 +1,71 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + serialized-entity + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + com.h2database + h2 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.serializedentity.App + + + + + + + + + diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java new file mode 100644 index 000000000..fb3b383e6 --- /dev/null +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java @@ -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. + * + *

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.

+ * + *

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).

+ * + */ +@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; + } +} diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java new file mode 100644 index 000000000..25ebddf89 --- /dev/null +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java @@ -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; + +} diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java new file mode 100644 index 000000000..f5d915446 --- /dev/null +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java @@ -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. + * + *

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; +} diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java new file mode 100644 index 000000000..4887ed9dd --- /dev/null +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java @@ -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; + } + +} diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java new file mode 100644 index 000000000..52ac9cd6e --- /dev/null +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java @@ -0,0 +1,23 @@ +package com.iluwatar.serializedentity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Tests that Serialized Entity example runs without errors. + */ +class AppTest { + + /** + * Issue: Add at least one assertion to this test case. + * + * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} + * throws an exception. + */ + + @Test + void shouldExecuteSerializedEntityWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java new file mode 100644 index 000000000..85d3ed87f --- /dev/null +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java @@ -0,0 +1,101 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class CountryTest { + + @Test + void testGetMethod() { + Country China = new Country( + 86, + "China", + "Asia", + "Chinese" + ); + + assertEquals(86, China.getCode()); + assertEquals("China", China.getName()); + assertEquals("Asia", China.getContinents()); + assertEquals("Chinese", China.getLanguage()); + } + + @Test + void testSetMethod() { + Country country = new Country( + 86, + "China", + "Asia", + "Chinese" + ); + + country.setCode(971); + country.setName("UAE"); + country.setContinents("West-Asia"); + country.setLanguage("Arabic"); + + assertEquals(971, country.getCode()); + assertEquals("UAE", country.getName()); + assertEquals("West-Asia", country.getContinents()); + assertEquals("Arabic", country.getLanguage()); + } + + @Test + void testSerializable(){ + // Serializing Country + try { + Country country = new Country( + 86, + "China", + "Asia", + "Chinese"); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("output.txt")); + objectOutputStream.writeObject(country); + objectOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + // De-serialize Country + try { + ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("output.txt")); + Country country = (Country) objectInputStream.readObject(); + objectInputStream.close(); + System.out.println(country); + + Country China = new Country( + 86, + "China", + "Asia", + "Chinese"); + + assertEquals(China, country); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/servant/pom.xml b/servant/pom.xml index 5e0b13915..65650364b 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -27,6 +27,7 @@ --> 4.0.0 + servant com.iluwatar java-design-patterns