|
| 1 | +--- |
| 2 | +title: Serialized LOB |
| 3 | +category: Structural |
| 4 | +language: en |
| 5 | +tag: |
| 6 | + - Data access |
| 7 | +--- |
| 8 | + |
| 9 | +## Intent |
| 10 | + |
| 11 | +* Object models often contain complicated graphs of small objects. Much of the information in these |
| 12 | + structures isn’t in the objects but in the links between them. |
| 13 | +* Objects don’t have to be persisted as table rows related to each other. |
| 14 | +* Another form of persistence is serialization, where a whole graph of objects is written out as a |
| 15 | + single large object (LOB) in a table. |
| 16 | + |
| 17 | +## Explanation |
| 18 | + |
| 19 | +**In plain words** |
| 20 | + |
| 21 | +> The Forest here represents the object graph. |
| 22 | +> A forest contains animals and plants. As is in real life the forest object contains plants and |
| 23 | +> animals where some animals eat other animals and some eat only plants. |
| 24 | +> * These relationships are maintained in the Forest Object. |
| 25 | +> * There are 2 types of Serializers available ClobSerializer and BlobSerializer. |
| 26 | +> * ClobSerializer uses character or textual based serialization, here the Object graph is converted |
| 27 | +> * to XML and then stored as text in DB. |
| 28 | +> * BlobSerializer uses binary data for serialization, here the Object graph is converted to Byte |
| 29 | +> * Array and then stored as a blob in DB. |
| 30 | +
|
| 31 | +**Programmatic Example** |
| 32 | + |
| 33 | +* Here is the `Animal` class. It represents the Animal object in the Forest. It contains the name of |
| 34 | + the animals in the forest and details of what they eat from the forest plants/animals or both. |
| 35 | + |
| 36 | +```java |
| 37 | +/** |
| 38 | + * Creates an object Forest which contains animals and plants as its constituents. Animals may eat |
| 39 | + * plants or other animals in the forest. |
| 40 | + */ |
| 41 | +@Data |
| 42 | +@NoArgsConstructor |
| 43 | +@AllArgsConstructor |
| 44 | +public class Forest implements Serializable { |
| 45 | + |
| 46 | + private String name; |
| 47 | + private final Set<Animal> animals = new HashSet<>(); |
| 48 | + private final Set<Plant> plants = new HashSet<>(); |
| 49 | + |
| 50 | + /** |
| 51 | + * Provides the representation of Forest in XML form. |
| 52 | + * |
| 53 | + * @return XML Element |
| 54 | + */ |
| 55 | + public Element toXmlElement() throws ParserConfigurationException { |
| 56 | + Document xmlDoc = getXmlDoc(); |
| 57 | + |
| 58 | + Element forestXml = xmlDoc.createElement("Forest"); |
| 59 | + forestXml.setAttribute("name", name); |
| 60 | + |
| 61 | + Element animalsXml = xmlDoc.createElement("Animals"); |
| 62 | + for (Animal animal : animals) { |
| 63 | + Element animalXml = animal.toXmlElement(xmlDoc); |
| 64 | + animalsXml.appendChild(animalXml); |
| 65 | + } |
| 66 | + forestXml.appendChild(animalsXml); |
| 67 | + |
| 68 | + Element plantsXml = xmlDoc.createElement("Plants"); |
| 69 | + for (Plant plant : plants) { |
| 70 | + Element plantXml = plant.toXmlElement(xmlDoc); |
| 71 | + plantsXml.appendChild(plantXml); |
| 72 | + } |
| 73 | + forestXml.appendChild(plantsXml); |
| 74 | + return forestXml; |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * Returns XMLDoc to use for XML creation. |
| 79 | + * |
| 80 | + * @return XML DOC Object |
| 81 | + * @throws ParserConfigurationException {@inheritDoc} |
| 82 | + */ |
| 83 | + private Document getXmlDoc() throws ParserConfigurationException { |
| 84 | + return DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().newDocument(); |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Parses the Forest Object from the input XML Document. |
| 89 | + * |
| 90 | + * @param document the XML document from which the Forest is to be parsed |
| 91 | + */ |
| 92 | + public void createObjectFromXml(Document document) { |
| 93 | + name = document.getDocumentElement().getAttribute("name"); |
| 94 | + NodeList nodeList = document.getElementsByTagName("*"); |
| 95 | + iterateXmlForAnimalAndPlants(nodeList, animals, plants); |
| 96 | + } |
| 97 | + |
| 98 | + @Override |
| 99 | + public String toString() { |
| 100 | + StringBuilder sb = new StringBuilder("\n"); |
| 101 | + sb.append("Forest Name = ").append(name).append("\n"); |
| 102 | + sb.append("Animals found in the ").append(name).append(" Forest: \n"); |
| 103 | + for (Animal animal : animals) { |
| 104 | + sb.append("\n--------------------------\n"); |
| 105 | + sb.append(animal.toString()); |
| 106 | + sb.append("\n--------------------------\n"); |
| 107 | + } |
| 108 | + sb.append("\n"); |
| 109 | + sb.append("Plants in the ").append(name).append(" Forest: \n"); |
| 110 | + for (Plant plant : plants) { |
| 111 | + sb.append("\n--------------------------\n"); |
| 112 | + sb.append(plant.toString()); |
| 113 | + sb.append("\n--------------------------\n"); |
| 114 | + } |
| 115 | + return sb.toString(); |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +* Here is the `LobSerializer` abstract class. It provides the specification to serialize and |
| 121 | + deserialize the input object and persist and load that object into a DB. |
| 122 | + |
| 123 | +```java |
| 124 | +/** |
| 125 | + * A LobSerializer can be used to create an instance of a serializer which can serialize and |
| 126 | + * deserialize an object and persist and load that object into a DB. from their Binary |
| 127 | + * Representation. |
| 128 | + */ |
| 129 | +public abstract class LobSerializer implements Serializable, Closeable { |
| 130 | + |
| 131 | + private final transient DatabaseService databaseService; |
| 132 | + |
| 133 | + /** |
| 134 | + * Constructor initializes {@link LobSerializer#databaseService}. |
| 135 | + * |
| 136 | + * @param dataTypeDb Input provides type of Data to be stored by the Data Base Service |
| 137 | + * @throws SQLException If any issue occurs during instantiation of DB Service or during startup. |
| 138 | + */ |
| 139 | + protected LobSerializer(String dataTypeDb) throws SQLException { |
| 140 | + databaseService = new DatabaseService(dataTypeDb); |
| 141 | + databaseService.startupService(); |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * Provides the specification to Serialize the input object. |
| 146 | + * |
| 147 | + * @param toSerialize Input Object to serialize |
| 148 | + * @return Serialized Object |
| 149 | + * @throws ParserConfigurationException if any issue occurs during parsing of input object |
| 150 | + * @throws TransformerException if any issue occurs during Transformation |
| 151 | + * @throws IOException if any issues occur during reading object |
| 152 | + */ |
| 153 | + public abstract Object serialize(Forest toSerialize) |
| 154 | + throws ParserConfigurationException, TransformerException, IOException; |
| 155 | + |
| 156 | + /** |
| 157 | + * Saves the object to DB with the provided ID. |
| 158 | + * |
| 159 | + * @param id key to be sent to DB service |
| 160 | + * @param name Object name to store in DB |
| 161 | + * @param object Object to store in DB |
| 162 | + * @return ID with which the object is stored in DB |
| 163 | + * @throws SQLException if any issue occurs while saving to DB |
| 164 | + */ |
| 165 | + public int persistToDb(int id, String name, Object object) throws SQLException { |
| 166 | + databaseService.insert(id, name, object); |
| 167 | + return id; |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Loads the object from db using the ID and column name. |
| 172 | + * |
| 173 | + * @param id to query the DB |
| 174 | + * @param columnName column from which object is to be extracted |
| 175 | + * @return Object from DB |
| 176 | + * @throws SQLException if any issue occurs while loading from DB |
| 177 | + */ |
| 178 | + public Object loadFromDb(int id, String columnName) throws SQLException { |
| 179 | + return databaseService.select(id, columnName); |
| 180 | + } |
| 181 | + |
| 182 | + /** |
| 183 | + * Provides the specification to Deserialize the input object. |
| 184 | + * |
| 185 | + * @param toDeserialize object to deserialize |
| 186 | + * @return Deserialized Object |
| 187 | + * @throws ParserConfigurationException If issue occurs during parsing of input object |
| 188 | + * @throws IOException if any issues occur during reading object |
| 189 | + * @throws SAXException if any issues occur during reading object for XML parsing |
| 190 | + */ |
| 191 | + public abstract Forest deSerialize(Object toDeserialize) |
| 192 | + throws ParserConfigurationException, IOException, SAXException, ClassNotFoundException; |
| 193 | + |
| 194 | + @Override |
| 195 | + public void close() { |
| 196 | + try { |
| 197 | + databaseService.shutDownService(); |
| 198 | + } catch (SQLException e) { |
| 199 | + throw new RuntimeException(e); |
| 200 | + } |
| 201 | + } |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +* Here is the `ClobSerializer` class. It extends the `LobSerializer` abstract class and provides the |
| 206 | + implementation to serialize and deserialize the input object and persist and load that object into |
| 207 | + a DB using ClobSerializer. |
| 208 | +* Objects are serialized using character or textual based serialization |
| 209 | + using XML to represent the object graph. |
| 210 | + |
| 211 | +```java |
| 212 | + |
| 213 | +/** |
| 214 | + * Creates a Serializer that uses Character based serialization and deserialization of objects graph |
| 215 | + * to and from XML Representation. |
| 216 | + */ |
| 217 | +public class ClobSerializer extends LobSerializer { |
| 218 | + |
| 219 | + public static final String TYPE_OF_DATA_FOR_DB = "TEXT"; |
| 220 | + |
| 221 | + public ClobSerializer() { |
| 222 | + super(TYPE_OF_DATA_FOR_DB); |
| 223 | + } |
| 224 | + |
| 225 | + /** |
| 226 | + * Converts the input node to its XML String Representation. |
| 227 | + * |
| 228 | + * @param node XML Node that is to be converted to string |
| 229 | + * @return String representation of XML parsed from the Node |
| 230 | + * @throws TransformerException If any issues occur in Transformation from Node to XML |
| 231 | + */ |
| 232 | + private static String elementToXmlString(Element node) throws TransformerException { |
| 233 | + StringWriter sw = new StringWriter(); |
| 234 | + Transformer t = TransformerFactory.newDefaultInstance().newTransformer(); |
| 235 | + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); |
| 236 | + t.setOutputProperty(OutputKeys.INDENT, "yes"); |
| 237 | + t.transform(new DOMSource(node), new StreamResult(sw)); |
| 238 | + return sw.toString(); |
| 239 | + } |
| 240 | + |
| 241 | + /** |
| 242 | + * Serializes the input object graph to its XML Representation using DOM Elements. |
| 243 | + * |
| 244 | + * @param forest Object which is to be serialized |
| 245 | + * @return Serialized object |
| 246 | + * @throws ParserConfigurationException If any issues occur in parsing input object |
| 247 | + * @throws TransformerException If any issues occur in Transformation from Node to XML |
| 248 | + */ |
| 249 | + @Override |
| 250 | + public Object serialize(Forest forest) throws ParserConfigurationException, TransformerException { |
| 251 | + Element xmlElement = forest.toXmlElement(); |
| 252 | + return elementToXmlString(xmlElement); |
| 253 | + } |
| 254 | + |
| 255 | + /** |
| 256 | + * Deserializes the input XML string using DOM Parser and return its Object Graph Representation. |
| 257 | + * |
| 258 | + * @param toDeserialize Input Object to De-serialize |
| 259 | + * @return Deserialized Object |
| 260 | + * @throws ParserConfigurationException If any issues occur in parsing input object |
| 261 | + * @throws IOException if any issues occur during reading object |
| 262 | + * @throws SAXException If any issues occur in Transformation from Node to XML |
| 263 | + */ |
| 264 | + @Override |
| 265 | + public Forest deSerialize(Object toDeserialize) |
| 266 | + throws ParserConfigurationException, IOException, SAXException { |
| 267 | + DocumentBuilder documentBuilder = DocumentBuilderFactory.newDefaultInstance() |
| 268 | + .newDocumentBuilder(); |
| 269 | + var stream = new ByteArrayInputStream(toDeserialize.toString().getBytes()); |
| 270 | + Document parsed = documentBuilder.parse(stream); |
| 271 | + Forest forest = new Forest(); |
| 272 | + forest.createObjectFromXml(parsed); |
| 273 | + return forest; |
| 274 | + } |
| 275 | +} |
| 276 | +``` |
| 277 | + |
| 278 | +* Here is the `SlobSerializer` class. It extends the `LobSerializer` abstract class and provides the |
| 279 | + implementation to serialize and deserialize the input object and persist and load that object into |
| 280 | + a DB using ClobSerializer. |
| 281 | +* Objects are serialized using binary data based serialization objects a persisted as a BINARY/BLOB |
| 282 | + in DB. |
| 283 | + |
| 284 | +```java |
| 285 | +/** |
| 286 | + * Creates a Serializer that uses Binary serialization and deserialization of objects graph to and |
| 287 | + * from their Binary Representation. |
| 288 | + */ |
| 289 | +public class BlobSerializer extends LobSerializer { |
| 290 | + |
| 291 | + public static final String TYPE_OF_DATA_FOR_DB = "BINARY"; |
| 292 | + |
| 293 | + public BlobSerializer() throws SQLException { |
| 294 | + super(TYPE_OF_DATA_FOR_DB); |
| 295 | + } |
| 296 | + |
| 297 | + /** |
| 298 | + * Serializes the input object graph to its Binary Representation using Object Stream. |
| 299 | + * |
| 300 | + * @param toSerialize Object which is to be serialized |
| 301 | + * @return Serialized object |
| 302 | + * @throws IOException {@inheritDoc} |
| 303 | + */ |
| 304 | + @Override |
| 305 | + public Object serialize(Forest toSerialize) throws IOException { |
| 306 | + ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 307 | + ObjectOutputStream oos = new ObjectOutputStream(baos); |
| 308 | + oos.writeObject(toSerialize); |
| 309 | + oos.close(); |
| 310 | + return new ByteArrayInputStream(baos.toByteArray()); |
| 311 | + } |
| 312 | + |
| 313 | + /** |
| 314 | + * Deserializes the input Byte Array Stream using Object Stream and return its Object Graph |
| 315 | + * Representation. |
| 316 | + * |
| 317 | + * @param toDeserialize Input Object to De-serialize |
| 318 | + * @return Deserialized Object |
| 319 | + * @throws ClassNotFoundException {@inheritDoc} |
| 320 | + * @throws IOException {@inheritDoc} |
| 321 | + */ |
| 322 | + @Override |
| 323 | + public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoundException { |
| 324 | + InputStream bis = (InputStream) toDeserialize; |
| 325 | + Forest forest; |
| 326 | + try (ObjectInput in = new ObjectInputStream(bis)) { |
| 327 | + forest = (Forest) in.readObject(); |
| 328 | + } |
| 329 | + return forest; |
| 330 | + } |
| 331 | +} |
| 332 | +``` |
| 333 | + |
| 334 | +## Class diagram |
| 335 | + |
| 336 | + |
| 337 | + |
| 338 | +## Applicability |
| 339 | + |
| 340 | +* This pattern works best when you can chop out a piece of the object model and use it to represent |
| 341 | + the LOB. |
| 342 | +* Think of a LOB as a way to take a bunch of objects that aren’t likely to be queried from any SQL |
| 343 | + route outside the application. |
| 344 | +* This graph can then be hooked into the SQL schema. Serialized LOB works poorly when you have |
| 345 | + objects outside the LOB reference objects buried in it. |
| 346 | +* Serialized LOB isn’t considered as often as it might be. XML makes it much more attractive since |
| 347 | + it yields an easy-to-implement textual approach. |
| 348 | +* Its main disadvantage is that you can’t query the structure using SQL. |
| 349 | +* SQL extensions appear to get at XML data within a field, but that’s still not the same (or |
| 350 | + portable). |
| 351 | + |
| 352 | +## Credits |
| 353 | + |
| 354 | +* [Serialized LOB](https://martinfowler.com/eaaCatalog/serializedLOB.html) by Martin Fowler |
0 commit comments