Back to Home

Work with resources, or how I pushed @Cleanup

java · lombok · cleanup

Work with resources, or how I pushed @Cleanup

This is a fictional story, and all coincidences are random.

Finally, the development team of Unknown Ltd. released the release on time. Andrew, head of development, systems architect, South, and simple ordinary developer, Bob, got together for planning.

In the coming quarter, they decided to take more tasks from those. debt, since almost the entire previous one was devoted to fixing bugs and performing urgent improvements for specific clients.

Everyone sat comfortably and began to discuss the upcoming plan. Bob immediately drew attention to the task of processing the generation of documents. The essence of the task was that the generated documents consist of a scheme and settings for generating and the document itself. When saving to the database, the document is serialized in XML, converted to a byte stream and compressed, and then everything is standard - it is placed in a BLOB column. When you need to display or upload a document in the system, then everything is repeated with the exact opposite, and voila, the document flaunts on the client’s screen. So, everything is simple. But, as you know, the devil is in the details. To regenerate the document, if you need to change the settings, you have to download the entire document from the database, although its contents are completely unnecessary. Ai-ya-yay. Discussed the problem. Came to the conclusion,

  • remove unnecessary entities, modify existing ones, create new ones to share the scheme with the settings and the document data itself
  • write a migration that will create a new table for storing the schema and data, in which there will be two columns, one of the CLOB type for storing the XML schema, and the other of the BLOB type for storing the still compressed XML with data

They decided on that. We went on to discuss the remaining issues.

An hour or a half passed.

Bob returned from planning enthusiastic, not having time to sit down at the workplace, he transferred the task to "In Work" and began execution. About two days passed, and the first part was completed. Bob safely committed the changes and sent them for review. In order not to waste time in vain, he began to carry out the second part - migration. After some time, the following migrant code was born:

public class MigratorV1  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
    public void migrate() throws Exception {
    	PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
    	ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data");
    	while (oldIdResult.next()) {
    		long id = oldIdResult.getLong(1);
    		selectOldContent.setLong(1, id);
        	ResultSet oldContentResult = selectOldContent.executeQuery();
        	oldContentResult.next();
        	Blob oldContent = oldContentResult.getBlob(1);
    		Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
    		StringWriter newSchemeWriter = new StringWriter();
    		XMLStreamWriter newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
    		ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
    		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
    		XMLStreamWriter newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
    		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
    			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
    		});
    		String newScheme = newSchemeWriter.toString();
    		byte[] newData = newDataOutput.toByteArray();
    		StringReader newSchemeReader = new StringReader(newScheme);
    		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
    		insertNewContent.setLong(1, id);
    		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
    		insertNewContent.setBlob(3, newDataInput, newData.length);
    		insertNewContent.executeUpdate();
    	}
    }
}

To use the migrator, the client code must create or somehow inject the migrator and call the migrate () method on it. That's all.

Something seems to be wrong, Bob thought. Well, of course, he forgot to free up resources. Imagine if a client has several hundred thousand documents on production and we don’t free up resources. Bob quickly fixed the problem:

public class MigratorV2  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
    public void migrate() throws Exception {
    	try (
			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
	    	ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data");
    	){
    		while (oldIdResult.next()) {
        		long id = oldIdResult.getLong(1);
        		selectOldContent.setLong(1, id);
        		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
        			oldContentResult.next();
        			String newScheme;
        			byte[] newData;
        			Blob oldContent = null;
        			try {
        				oldContent = oldContentResult.getBlob(1);
        				try (
        					Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
        					StringWriter newSchemeWriter = new StringWriter();
    						ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                    		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
        				){
        					XMLStreamWriter newSchemeXMLWriter = null;
        					XMLStreamWriter newDataXMLWriter = null;
        					try {
        						newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
                        		newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
                        		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                        			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                        		});
        					} finally {
        						if (newSchemeXMLWriter != null) {
                					try {
                						newSchemeXMLWriter.close();
                					} catch (XMLStreamException e) {}
                				}
        						if (newDataXMLWriter != null) {
                					try {
                						newDataXMLWriter.close();
                					} catch (XMLStreamException e) {}
                				}
        					}
        					newScheme = newSchemeWriter.toString();
                    		newData = newDataOutput.toByteArray();
        				}
        			} finally {
        				if (oldContent != null) {
        					try {
        						oldContent.free();
        					} catch (SQLException e) {}
        				}
        			}
        			try (
        				StringReader newSchemeReader = new StringReader(newScheme);
                		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
            		){
            			insertNewContent.setLong(1, id);
                		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                		insertNewContent.setBlob(3, newDataInput, newData.length);
                		insertNewContent.executeUpdate();
            		}
        		}
        	}
    	}
    }
}

Oh God! Thought Bob. How to deal with this now? This code is difficult to understand not only for another developer, but for me, if I return to it, suppose in a month to fix or add something. We need to decompose, Bob thought, and break up the independent parts of the code into methods:

public class MigratorV3  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
	@RequiredArgsConstructor
	private static class NewData {
		final String scheme;
		final byte[] data;
	}
	private List loadIds() throws Exception {
		List ids = new ArrayList<>();
		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
			while (oldIdResult.next()) {
				ids.add(oldIdResult.getLong(1));
			}
		}
		return ids;
	}
	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		selectOldContent.setLong(1, id);
		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
			oldContentResult.next();
			return oldContentResult.getBlob(1);
		}
	}
	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
		XMLStreamWriter newSchemeXMLWriter = null;
		XMLStreamWriter newDataXMLWriter = null;
		try {
			newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
    		newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
    		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
    			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
    		});
		} finally {
			if (newSchemeXMLWriter != null) {
				try {
					newSchemeXMLWriter.close();
				} catch (XMLStreamException e) {}
			}
			if (newDataXMLWriter != null) {
				try {
					newDataXMLWriter.close();
				} catch (XMLStreamException e) {}
			}
		}
	}
	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		Blob oldContent = null;
		try {
			oldContent = loadOldContent(selectOldContent, id);
			try (
				Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
				StringWriter newSchemeWriter = new StringWriter();
				ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
        		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
			){
				oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
				return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
			}
		} finally {
			if (oldContent != null) {
				try {
					oldContent.free();
				} catch (SQLException e) {}
			}
		}
	}
	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
		try (
			StringReader newSchemeReader = new StringReader(newScheme);
    		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
		){
			insertNewContent.setLong(1, id);
    		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
    		insertNewContent.setBlob(3, newDataInput, newData.length);
    		insertNewContent.executeUpdate();
		}
	}
    public void migrate() throws Exception {
    	List ids = loadIds();
    	try (
			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
    	){
    		for (Long id : ids) {
    			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
    			storeNewData(insertNewContent, id, newData.scheme, newData.data);
    		}
    	}
    }
}

Everything, as before, the client code creates a migrator and calls migrate (). Inside, all the identifiers of the current documents are loaded, and for each identifier, the contents of the documents are loaded, which, using the SAX parser, is disassembled into parts belonging to the schema and data so that they can then be saved in a new table, but separately in different columns.

It seems to be a little better. But Bob was sad. Why XMLStreamWriter and Blob do not implement AutoСloseable, he thought and started writing wrappers:

public class MigratorV4  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
	@RequiredArgsConstructor
	private static class NewData {
		final String scheme;
		final byte[] data;
	}
	@RequiredArgsConstructor
	private static class SmartXMLStreamWriter implements AutoCloseable {
		final XMLStreamWriter writer;
		@Override
		public void close() throws Exception {
			writer.close();
		}
	}
	@RequiredArgsConstructor
	private static class SmartBlob implements AutoCloseable {
		final Blob blob;
		@Override
		public void close() throws Exception {
			blob.free();
		}
	}
	private List loadIds() throws Exception {
		List ids = new ArrayList<>();
		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
			while (oldIdResult.next()) {
				ids.add(oldIdResult.getLong(1));
			}
		}
		return ids;
	}
	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		selectOldContent.setLong(1, id);
		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
			oldContentResult.next();
			return oldContentResult.getBlob(1);
		}
	}
	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
		try (
			SmartXMLStreamWriter newSchemeXMLWriter = new SmartXMLStreamWriter(xmlFactory.createXMLStreamWriter(newSchemeWriter));
			SmartXMLStreamWriter newDataXMLWriter = new SmartXMLStreamWriter(xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8"));
		){
			xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
				// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
			});
		}
	}
	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		try (
			SmartBlob oldContent = new SmartBlob(loadOldContent(selectOldContent, id));
			Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.blob.getBinaryStream()));
			StringWriter newSchemeWriter = new StringWriter();
			ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
    		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
		){
			oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
			return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
		}
	}
	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
		try (
			StringReader newSchemeReader = new StringReader(newScheme);
    		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
		){
			insertNewContent.setLong(1, id);
    		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
    		insertNewContent.setBlob(3, newDataInput, newData.length);
    		insertNewContent.executeUpdate();
		}
	}
    public void migrate() throws Exception {
    	List ids = loadIds();
    	try (
			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
    	){
    		for (Long id : ids) {    			
    			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
    			storeNewData(insertNewContent, id, newData.scheme, newData.data);
    		}
    	}
    }
}

Two wrappers SmartXMLStreamWriter and SmartBlob were written that automatically closed XMLStreamWriter and Blob in try-with-resources.

And if I have more resources that do not implement AutoCloseable, will I have to write wrappers again? Bob turned to the South for help. The South, having a little thought, produced an original solution using the capabilities of Java 8:

public class MigratorV5  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
	@RequiredArgsConstructor
	private static class NewData {
		final String scheme;
		final byte[] data;
	}
	private List loadIds() throws Exception {
		List ids = new ArrayList<>();
		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
			while (oldIdResult.next()) {
				ids.add(oldIdResult.getLong(1));
			}
		}
		return ids;
	}
	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		selectOldContent.setLong(1, id);
		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
			oldContentResult.next();
			return oldContentResult.getBlob(1);
		}
	}
	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
		XMLStreamWriter newSchemeXMLWriter;
		XMLStreamWriter newDataXMLWriter;
		try (
			AutoCloseable fake1 = (newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter))::close;
			AutoCloseable fake2 = (newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8"))::close;
		){
			xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
				// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
			});
		}
	}
	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		Blob oldContent;
		try (
			AutoCloseable fake = (oldContent = loadOldContent(selectOldContent, id))::free;
			Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
			StringWriter newSchemeWriter = new StringWriter();
			ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
    		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
		){
			oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
			return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
		}
	}
	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
		try (
			StringReader newSchemeReader = new StringReader(newScheme);
    		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
		){
			insertNewContent.setLong(1, id);
    		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
    		insertNewContent.setBlob(3, newDataInput, newData.length);
    		insertNewContent.executeUpdate();
		}
	}
    public void migrate() throws Exception {
    	List ids = loadIds();
    	try (
			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
    	){
    		for (Long id : ids) {    			
    			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
    			storeNewData(insertNewContent, id, newData.scheme, newData.data);
    		}
    	}
    }
}

Yes, yes, yes, exactly what you thought: he took the opportunity to transfer the method. The code is just awful. But you don’t need to write wrappers, Bob thought and cried.

And then he drew attention to the annotation, which he had so actively used: @RequiredArgsConstructor. Eureka! The Lombok library has the @Cleanup annotation, which was born in order to console a Java programmer who has lost all hope. At the compilation stage, it adds try-finally to the bytecode and automatically adds the safe closing code for resources. Moreover, she knows how to work with any method of freeing resources, be it close (), free () or some other, the main thing is to tell her about this (although she is smart herself and swears if she does not find a suitable method).

And Bob rewrote the problem spots using @Cleanup:

public class MigratorV6  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
	@RequiredArgsConstructor
	private static class NewData {
		final String scheme;
		final byte[] data;
	}
	private List loadIds() throws Exception {
		List ids = new ArrayList<>();
		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
			while (oldIdResult.next()) {
				ids.add(oldIdResult.getLong(1));
			}
		}
		return ids;
	}
	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		selectOldContent.setLong(1, id);
		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
			oldContentResult.next();
			return oldContentResult.getBlob(1);
		}
	}
	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
		@Cleanup XMLStreamWriter newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
		@Cleanup XMLStreamWriter newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
		});
	}
	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
		@Cleanup("free") Blob oldContent = loadOldContent(selectOldContent, id);
		try (
			Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
			StringWriter newSchemeWriter = new StringWriter();
			ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
    		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
		){
			oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
			return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
		}
	}
	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
		try (
			StringReader newSchemeReader = new StringReader(newScheme);
    		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
		){
			insertNewContent.setLong(1, id);
    		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
    		insertNewContent.setBlob(3, newDataInput, newData.length);
    		insertNewContent.executeUpdate();
		}
	}
    public void migrate() throws Exception {
    	List ids = loadIds();
    	try (
			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
    	){
    		for (Long id : ids) {    			
    			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
    			storeNewData(insertNewContent, id, newData.scheme, newData.data);
    		}
    	}
    }
}

Satisfied with the elegant, and most importantly, out of the box decision, Bob made the long-awaited commit and gave the code for the review.

Nothing boded ill. But troubles always lie in wait around the corner. The commit did not go through the review, South and Andrew did not approve @Cleanup. Just two places where non-AutoCloseable resources are used, they said. What profit will it give us? We do not like this annotation! How are we going to debug the code in case of what? And so on. Bob fought mercilessly, but all attempts were in vain. And then he made another attempt to prove convenience and rolled out the following code:

public class MigratorV7  {
	private Connection conn; // Injected
	private SAXParser xmlParser; // Injected
	private XMLOutputFactory xmlFactory; // Injected
    public void migrate() throws Exception {
    	@Cleanup PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
    	@Cleanup PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
    	@Cleanup ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data");
    	while (oldIdResult.next()) {
    		long id = oldIdResult.getLong(1);
    		selectOldContent.setLong(1, id);
    		@Cleanup ResultSet oldContentResult = selectOldContent.executeQuery();
        	oldContentResult.next();
        	@Cleanup("free") Blob oldContent = oldContentResult.getBlob(1);
        	@Cleanup Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
        	@Cleanup StringWriter newSchemeWriter = new StringWriter();
        	@Cleanup XMLStreamWriter newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
        	ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
        	@Cleanup GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
        	@Cleanup XMLStreamWriter newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
    		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
    			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
    		});
    		String newScheme = newSchemeWriter.toString();
    		byte[] newData = newDataOutput.toByteArray();
    		@Cleanup StringReader newSchemeReader = new StringReader(newScheme);
    		@Cleanup ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
    		insertNewContent.setLong(1, id);
    		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
    		insertNewContent.setBlob(3, newDataInput, newData.length);
    		insertNewContent.executeUpdate();
    	}
    }
}

Yes Yes. He removed all the additional methods and returned the sequential procedural code again. Efficiency, of course, he did not have time to check, because he wanted to show simplicity. Probably, not only he will understand this code in a month, but any other who will have to read it.

But he again did not find support. No matter how he fought against the wall, the wall turned out to be stronger. And he gave up. As a result, the MigratorV5 code was rolled out to production - the one where Java 8 features are so clumsily used.

Epilogue.

Of course, the code that was given is far from ideal, and you still have to comb it, some things can be rewritten in a completely different way, for example, using template code. The latter option is generally the embodiment of a procedural programming style, which is not very good (but it is understandable when reading from top to bottom). But that's not the point. @Cleanup is a cool annotation that helps exactly in such moments when we cannot use try-with-resources, it saves us from over-nesting blocks of code in one another if we do not break the operations into methods. She does not need to get involved, but if necessary, why not?

Read Next