<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vladimir Nemergut</title>
    <description>The latest articles on DEV Community by Vladimir Nemergut (@vladonemo).</description>
    <link>https://dev.to/vladonemo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F312083%2Fb229a039-6064-4e91-9496-52d6f4cf2a23.jpeg</url>
      <title>DEV Community: Vladimir Nemergut</title>
      <link>https://dev.to/vladonemo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vladonemo"/>
    <language>en</language>
    <item>
      <title>Splitting Liquibase changelog? No problem.</title>
      <dc:creator>Vladimir Nemergut</dc:creator>
      <pubDate>Tue, 24 Mar 2020 11:03:17 +0000</pubDate>
      <link>https://dev.to/vladonemo/splitting-liquibase-changelong-no-problem-2a4l</link>
      <guid>https://dev.to/vladonemo/splitting-liquibase-changelong-no-problem-2a4l</guid>
      <description>&lt;h1&gt;
  
  
  What is Liquibase?
&lt;/h1&gt;

&lt;p&gt;As &lt;a href="https://en.wikipedia.org/wiki/Liquibase"&gt;Wikipedia&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Liquibase is an open-source database-independent library for tracking, managing and applying database schema changes. It was started in 2006 to allow easier tracking of database changes, especially in an agile software development environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I find Liquibase as a neat tool to migrate your database automatically. DB migration itself is a very complicated topic anyway, outside the scope of this article.&lt;/p&gt;

&lt;p&gt;Liquibase can run as a standalone tool or it can be integrated into your application. It's easy to add it to Spring context. &lt;/p&gt;

&lt;p&gt;Spring Boot makes it even easier. Liquibase is autoconfigured if you enable it in the properties file, you have Liquibase in the classpath and you have &lt;code&gt;DataSource&lt;/code&gt; in the context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;liquibase.change-log=classpath:changelog.xml
liquibase.enabled=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Liquibase makes the MockMvc testing very simple, too. One can configure it to create the H2 database for the testing purposes. &lt;/p&gt;

&lt;h1&gt;
  
  
  How Liquibase works?
&lt;/h1&gt;

&lt;p&gt;Liquibase reads the xml changelog file and figures out what &lt;em&gt;changesets&lt;/em&gt; it needs to apply. It uses the &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; table in your DB (&lt;code&gt;DataSource&lt;/code&gt;) for this purpose. The &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; contains the list of changesets that are already applied with their &lt;code&gt;ID&lt;/code&gt;, &lt;code&gt;FILENAME&lt;/code&gt;, &lt;code&gt;MD5SUM&lt;/code&gt;, &lt;code&gt;AUTHOR&lt;/code&gt; and few other properties.&lt;/p&gt;

&lt;p&gt;The logic is relatively simple. Just by comparing the changelog with the table Liquibase knows what changesets it needs to apply. There are, however, few gotchas ...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Liquibase can only take one changelog file&lt;/li&gt;
&lt;li&gt;Liquibase determines the list of changesets to apply before applying them&lt;/li&gt;
&lt;li&gt;the actual DB can get out of sync with the &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; table. E.g. if you manually modify database, or so on&lt;/li&gt;
&lt;li&gt;if Liquibase fails to apply a changeset, it fails immediately and won't continue with next datasets&lt;/li&gt;
&lt;li&gt;if Liquibase is running in Spring app as a bean, it executes during application startup, hence if it fails, then the application won't start&lt;/li&gt;
&lt;li&gt;changesets are not atomic. It can happen that part of the changeset passes, it modifies the DB properly, and next part fails. The changeset record won't go into &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; table. Hence it leaves the DB in the state that requires manual repair (e.g. reverting the part of the changeset and letting Liquibase to run again)&lt;/li&gt;
&lt;li&gt;the changesets can't be modified. If you modify changeset after it was applied in your db, then Liquibase fails stating that the MD5SUM doesn't match.&lt;/li&gt;
&lt;li&gt;The ID is not the unique identifier of the changeset. It is in fact the combination of &lt;code&gt;ID&lt;/code&gt;, &lt;code&gt;FILENAME&lt;/code&gt; and &lt;code&gt;AUTHOR&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the changesets that are in the &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; and are not in the changelog files are ignored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, Liquibase has much more functionality. Just read the &lt;a href="https://www.liquibase.org/documentation/index.html"&gt;documentation&lt;/a&gt;. This is also out of scope of this article.&lt;/p&gt;

&lt;h1&gt;
  
  
  The changelog file grows over time
&lt;/h1&gt;

&lt;p&gt;Yep, if you don't define some strategy at the beginning, your changelog file will just grow bigger and bigger. On a large project it can be a couple of thousands of lines long with hundreds of changesets. There is a high code churn on the changelog file, too, so it will cause you some merging effort.&lt;/p&gt;

&lt;p&gt;There are a few alternatives that you should consider early on to avoid this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define multiple changelog files
&lt;/h2&gt;

&lt;p&gt;.. and &lt;code&gt;&amp;lt;include&amp;gt;&lt;/code&gt; them in the master changelog, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;databaseChangeLog&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog"&lt;/span&gt;
                   &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
                   &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog dbchangelog-3.5.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"feature1.xml"&lt;/span&gt; &lt;span class="na"&gt;relativeToChangelogFile=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"feature2.xml"&lt;/span&gt; &lt;span class="na"&gt;relativeToChangelogFile=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"feature3.xml"&lt;/span&gt; &lt;span class="na"&gt;relativeToChangelogFile=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The benefit of this one is obvious - less code churn, better organization. The problem comes if there are any logical dependencies between changesets across the files - e.g. if there are any relations defined between tables of multiple files. Since the Liquibase executes the changesets in sequence, it starts with &lt;code&gt;feature1.xml&lt;/code&gt;, continues with &lt;code&gt;feature2.xml&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Perhaps you can find out a better split key - based on target releases perhaps?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"release_0.1.0.0.xml"&lt;/span&gt; &lt;span class="na"&gt;relativeToChangelogFile=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"release_0.1.0.1.xml"&lt;/span&gt; &lt;span class="na"&gt;relativeToChangelogFile=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;include&lt;/span&gt; &lt;span class="na"&gt;file=&lt;/span&gt;&lt;span class="s"&gt;"release_1.0.0.0.xml"&lt;/span&gt; &lt;span class="na"&gt;relativeToChangelogFile=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure multiple Liquibase runs
&lt;/h2&gt;

&lt;p&gt;Since one run can only take one changelog file, just define multiple changelog files and let the Liquibase run multiple times.&lt;/p&gt;

&lt;p&gt;In your Spring (Boot) app just define multiple &lt;code&gt;liquibase&lt;/code&gt; beans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;liquibase.integration.spring.SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.DependsOn&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.sql.DataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultipleLiquiaseConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibaseRelease1&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:release_v1.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibaseRelease2&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:release_v2.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Both beans will be created in the context, hence 2 Liquibase runs will be performed. If you rely on the Spring Boot's autoconfiguration, your &lt;code&gt;entityManager&lt;/code&gt; bean will force you to have one bean called &lt;code&gt;liquibase&lt;/code&gt;. This is easy to do. Also, if your changelogs need to run in a certain order, you can solve this with &lt;code&gt;@DependsOn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultipleLiquiaseConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibaseV1&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:release_v1.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@DependsOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"liquibaseV1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:release_v2.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note, that the last to run is called &lt;code&gt;liquibase&lt;/code&gt; (which your &lt;code&gt;entityManager&lt;/code&gt; depends on) and it points to the previous-to-run with &lt;code&gt;@DependsOn&lt;/code&gt; annotation.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to deal with long changelog?
&lt;/h1&gt;

&lt;p&gt;If you haven't applied any strategy early on, or you just joined a running project with legacy code, your changelog is already too big. Now, how to reduce it?&lt;/p&gt;

&lt;p&gt;You might say - well, I just split it to multiple files and use either of the 2 strategies as mentioned above. Well, not so fast! :) I mentioned earlier that the filename is important as it is used to determine if a changeset was applied or not. If you simply move existing changesets to another file, Liquibase would think that those changesets were not applied and in fact will try to apply them again. And it will fail as the DB already contains the changes. &lt;/p&gt;

&lt;p&gt;To describe the issue a bit better, just imagine a model situation having this &lt;code&gt;changelog.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;databaseChangeLog&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog"&lt;/span&gt;
                   &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
                   &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog dbchangelog-3.5.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"me"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"changeset1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;createTable&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"TABLE1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"COLUMN1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR2(10)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/createTable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"me"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"changeset2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;createTable&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"TABLE2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"COLUMN1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR2(10)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/createTable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And you do move the second changeset to &lt;code&gt;changelog2.xml&lt;/code&gt; and include &lt;code&gt;changelog2.xml&lt;/code&gt; in the &lt;code&gt;changelog.xml&lt;/code&gt;. Starting your app will fail with similar exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Table "TABLE1" already exists; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ok, it will work just fine in your unit tests, since the DB is created from scratch, but will fail if you run Liquibase to migrate the DB of your deployed instance. We all agree that this is bad ;)&lt;/p&gt;

&lt;p&gt;Luckily, we still have a few options left ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Change the &lt;code&gt;logicalFilePath&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Liquibase allows you to define so called logical file path of your changelog. This allows you to fake Liquibase that the changesets actually come from the same file. Imagine the &lt;code&gt;changelog2.xml&lt;/code&gt; would look like this now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;databaseChangeLog&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog"&lt;/span&gt;
                   &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
                   &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog dbchangelog-3.5.xsd"&lt;/span&gt;
                   &lt;span class="na"&gt;logicalFilePath=&lt;/span&gt;&lt;span class="s"&gt;"classpath:changelog.xml"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"me"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"changeset2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;createTable&lt;/span&gt; &lt;span class="na"&gt;tableName=&lt;/span&gt;&lt;span class="s"&gt;"TABLE2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;column&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"COLUMN1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"VARCHAR2(10)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/createTable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;loficalFilePath&lt;/code&gt; value there. Yes, this will work, Liquibase will treat this &lt;code&gt;changeset2&lt;/code&gt; as if it was previously defined in &lt;code&gt;changelog.xml&lt;/code&gt;. Perfect.&lt;/p&gt;

&lt;p&gt;Actually, this approach has also a few drawbacks that might (but might not) stop you from applying. If you don't store your changelog in the resources, but rather elsewhere in filesystem, your &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; will contain the full path to the file. If you then have multiple environments where you want to migrate DB and  your changelog file location vary, you have no way how to set the &lt;code&gt;logicalFilePath&lt;/code&gt;. Remember that it must match the previous value.&lt;/p&gt;

&lt;p&gt;Another issue is that this approach is not the best if your intent to split the changelog is to move the part of it to another package, module, and so on. &lt;/p&gt;

&lt;h2&gt;
  
  
  Use intermediate changelog
&lt;/h2&gt;

&lt;p&gt;If you intend to move part of your changelog to another module (e.g. you finally want to break that nasty monolith of yours to a few microservices having their own database), this approach might suite you the best. It contains some intermediate and temporary steps, but the outcome is what you want :)&lt;/p&gt;

&lt;p&gt;The first step is to move all the relevant changesets to another file elsewhere. In our example above we just move the &lt;code&gt;changeset2&lt;/code&gt; to &lt;code&gt;changelog2.xml&lt;/code&gt;. Now we need to fake Liquibase that those changesets didn't change. We do it by &lt;strong&gt;modifying the FILENAME&lt;/strong&gt; value in the database as part of the Liquibase changelog itself ;)&lt;/p&gt;

&lt;p&gt;Create one more (intermediate/temporary) changelog (let's call it &lt;code&gt;tmp-migration.xml&lt;/code&gt;) with just this one changeset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;databaseChangeLog&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog"&lt;/span&gt;
                   &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
                   &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://www.liquibase.org/xml/ns/dbchangelog dbchangelog-3.5.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;changeSet&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"moving changesets from changelog to changelog2"&lt;/span&gt; &lt;span class="na"&gt;author=&lt;/span&gt;&lt;span class="s"&gt;"Maros Kovacme"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;sql&amp;gt;&lt;/span&gt;
            UPDATE DATABASECHANGELOG
            SET
            FILENAME = REPLACE(FILENAME, 'changelog.xml', 'changelog2.xml'))
            WHERE
            ID IN (
            'changeset2'
            );
        &lt;span class="nt"&gt;&amp;lt;/sql&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/changeSet&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This changeset will replace the FILENAME column value in the DB from &lt;code&gt;classpath:changelog.xml&lt;/code&gt; to &lt;code&gt;classpath:changelog2.xml&lt;/code&gt;. When we then run Liquibase with the &lt;code&gt;changelog2.xml&lt;/code&gt;, it will think that all changesets are already applied. It is not possible to use just 2 changelog files for this purpose. Liquibase first calculates the list of changesets to be applied (per changelog file) and only then it will apply them. We need to modify the &lt;code&gt;FILENAME&lt;/code&gt; before it processes the second file. &lt;/p&gt;

&lt;p&gt;The last step we have to apply is to define the corresponding beans in our context in the right order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultipleLiquiaseConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibaseChangelog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:changelog.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@DependsOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"liquibaseChangelog"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibaseMigration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:tmp-migration.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"liquibase"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@DependsOn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"liquibaseMigration"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="nf"&gt;liquibaseChangelog2&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpringLiquibase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setChangeLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"classpath:changelog2.xml"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liquibase&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;changelog.xml&lt;/code&gt; will run first. The changeset2 exists in the &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; but not in the file, hence it is ignored. Then the &lt;code&gt;tmp-migration.xml&lt;/code&gt; runs and changes the &lt;code&gt;FILENAME&lt;/code&gt; column. The last will run the &lt;code&gt;changelog2.xml&lt;/code&gt;, but Liquibase will treat the changeset2 as already applied.&lt;/p&gt;

&lt;p&gt;Some time later (when you believe that all affected databases are already migrated) you might remove the &lt;code&gt;tmp-migration.xml&lt;/code&gt; together with it's bean. The changeset will stay in the &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; table but that's just a minor thing I believe.&lt;/p&gt;

&lt;p&gt;And then the next step could be to move the definition of beans to the contexts of your concrete microservices.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;There is always some way ;)&lt;/p&gt;

</description>
      <category>liquibase</category>
      <category>java</category>
      <category>devops</category>
      <category>database</category>
    </item>
    <item>
      <title>Reducing the maven build execution time</title>
      <dc:creator>Vladimir Nemergut</dc:creator>
      <pubDate>Mon, 16 Mar 2020 10:47:09 +0000</pubDate>
      <link>https://dev.to/vladonemo/reducing-the-maven-build-execution-time-1o7k</link>
      <guid>https://dev.to/vladonemo/reducing-the-maven-build-execution-time-1o7k</guid>
      <description>&lt;h1&gt;
  
  
  Current state
&lt;/h1&gt;

&lt;p&gt;We have a multi-module Spring Web MVC 4 application with about 100k lines of code. We use Maven and Azure DevOps. A simple build pipeline builds and runs all the unit tests - about 2.8k of them. Well, honestly, I would call most of them component tests or even integration tests. Let's clear out some definition at the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a unit test?
&lt;/h2&gt;

&lt;p&gt;There are many definitions of a unit test. I like the one by &lt;a href="http://sandordargo.com/blog/2020/01/01/the-art-of-unit-testing"&gt;Roy Osherove&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A unit test is an automated piece of code that invokes the unit of work being tested, and then checks some assumptions about a single end result of that unit. A unit test is almost always written using a unit testing framework. It can be written easily and runs quickly. It’s trustworthy, readable, and maintainable. It’s consistent in its results as long as production code hasn’t changed.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a nice article about unit tests in &lt;a href="https://martinfowler.com/bliki/UnitTest.html"&gt;Martin Fowler's bliki&lt;/a&gt; so I won't go into much details.&lt;/p&gt;

&lt;p&gt;Let's say that we expect the following from a unit test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast feedback (has to run fast)&lt;/li&gt;
&lt;li&gt;short (small amount of code)&lt;/li&gt;
&lt;li&gt;tests one thing (one logical assert)&lt;/li&gt;
&lt;li&gt;fails for a good reason&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a component test?
&lt;/h2&gt;

&lt;p&gt;Again a nice article in &lt;a href="https://martinfowler.com/bliki/ComponentTest.html"&gt;bliki&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Since our application uses Spring Data JPA, we have lots of repositories. Many of them contain custom query methods. We use &lt;a href="https://springtestdbunit.github.io/spring-test-dbunit/"&gt;Spring Test DBUnit&lt;/a&gt; to help us out testing them. The idea is simple - you setup a mockup database (H2), import some test data before a test, run a test, assert and then cleanup the DB end the end. Since this approach creates the DB and some (small) spring context with all necessary beans, I treat these tests as component tests. &lt;/p&gt;

&lt;p&gt;These tests run much longer than unit tests. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is an integration test?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://martinfowler.com/bliki/IntegrationTest.html"&gt;The bliki&lt;/a&gt; defines the purpose of integration tests as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The point of integration testing, as the name suggests, is to test whether many separately developed modules work together as expected. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our case, we have the the Spring Web MVC controllers. And those controllers expose RESTful API. For those we usually create a full spring context (with mocked H2 database) and use MockMvc to examine them. The beauty of such tests is that you test the API, including all underlying services (business logic) and repositories (persistence). The huge drawback is the execution time. It takes really long time to create Spring context (depends on how large your app is). And in many cases the context is destroyed and created fresh. This all adds up to the total build time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What tests do I need?
&lt;/h2&gt;

&lt;p&gt;If you are now biased to lean towards just unit tests, as they are fast to run, and want to ditch your component/integration tests as they are slow - hang on ... not so fast. You need The tests at &lt;em&gt;all levels&lt;/em&gt;. In a reasonable ratio. In large applications you might split the test runs to fast/slow to speedup the feedback loop. You also need UI driven tests, system tests, performance tests ... Ok, the whole topic about high quality testing of a software is very complex and you might start reading &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html"&gt;here&lt;/a&gt; if you are interested. Let's get back to our topic for now.&lt;/p&gt;

&lt;h1&gt;
  
  
  The path to improve the situation
&lt;/h1&gt;

&lt;p&gt;There is a &lt;a href="https://www.baeldung.com/spring-tests"&gt;nice article&lt;/a&gt; written about what you can do. It tells you to re-think whether you need that many integration tests and rewrite them with unit tests. As described above, do it with caution.&lt;/p&gt;

&lt;p&gt;It's important to define the test strategy early on. Otherwise the issue will just grow too big to finally conclude that you deal with lots of legacy tests that is expensive to change now. In companies and large projects this happens too often to simply ignore it. Legacy or not, there is still something we can do with reasonable effort and positive return of investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring context
&lt;/h2&gt;

&lt;p&gt;Due to many reasons our spring context is created in total 17 times during the test run. Every time it takes about 30 seconds to start. It is 8.5 minutes added to the test run. You can make use of the advice from the article above to make sure that the context is only created once. We could save 8 minutes here.&lt;/p&gt;

&lt;h2&gt;
  
  
  DBUnit
&lt;/h2&gt;

&lt;p&gt;If using DBUnit, each test starts with importing data to DB and ends with cleanup. There are several strategies for this. The Spring Test DBUnit expects these annotations on test method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@DatabaseSetup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data.xml"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@DatabaseTearDown&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data.xml"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The default strategy is &lt;code&gt;CLEAN_INSERT&lt;/code&gt; for both Setup and Teardown which in fact deletes all data from affected tables and insert them again. With pure DBUnit, you configure it explicitly, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AbstractDatabaseTester&lt;/span&gt; &lt;span class="n"&gt;databaseTester&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DataSourceDatabaseTester&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;databaseTester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSetUpOperation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DatabaseOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CLEAN_INSERT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;databaseTester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTearDownOperation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DatabaseOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DELETE_ALL&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Consider changing these strategies to something faster, e.g. instead of &lt;code&gt;CLEAN_INSERT&lt;/code&gt; for Setup, use just &lt;code&gt;INSERT&lt;/code&gt;. Of course, this will cause problems if your tables are not cleaned up properly by previous tests. The &lt;code&gt;CLEAN_INSERT&lt;/code&gt; of Teardown can be replaced with just &lt;code&gt;DELETE_ALL&lt;/code&gt; or even faster one &lt;code&gt;TRUNCATE_TABLE&lt;/code&gt;. There is one catch with &lt;code&gt;TRUNCATE_TABLE&lt;/code&gt; on H2 database, more details in the appendix. &lt;/p&gt;

&lt;p&gt;We have about 1200 tests that are running with DBUnit. Just imagine if we saved 100 milliseconds for setup/teardown. It would be 4 minutes saved with relatively low effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build time analysis
&lt;/h2&gt;

&lt;p&gt;However, our situation was a bit more complicated. Our build execution time grew from about 9 minutes to something about 25 minutes in avarage over the past 6 months.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oiTyNmc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b68dox88grh948kmfc9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oiTyNmc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b68dox88grh948kmfc9s.png" alt="Pipeline duration"&gt;&lt;/a&gt;&lt;br&gt;
(the sudden peaks are caused by the fact that we recycle the build agents every now and then and hence the maven cache is gone). &lt;/p&gt;

&lt;p&gt;Moreover, we experience some builds taking as long as 45 minutes and some of them even more then 1 hour. &lt;/p&gt;

&lt;p&gt;We did a simple analysis of the build logs. The tests are configured to output to the console with &lt;code&gt;TRACE&lt;/code&gt; level. There we could see that some operations took suddenly much longer than usual. For instance the DB teardown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2020-03-13T12:59:21.9660309Z INFO  DefaultPrepAndExpectedTestCase - cleanupData: about to clean up 13 tables=[...]
2020-03-13T12:59:29.0545094Z INFO  MockServletContext - Initializing Spring FrameworkServlet ''
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first message is the Teardown of DB from one test, the second message is starting a new spring context for another test. Note the timestamps. 7 seconds to cleanup the DB! Remember we do it 1200 times. After analyzing one build output (the build took 45 minutes) we calculated the total time spent on cleaning up the DB - 1800 seconds. 2/3 of the build spent on this. It surely can't take that long.&lt;/p&gt;

&lt;p&gt;Of course we already checked the infrastructure. The build agents are pretty decent &lt;a href="https://aws.amazon.com/ec2/instance-types/t2/"&gt;t2.large&lt;/a&gt; EC2 instances with 2 vCPUs and 8GiB RAM powering Ubuntu. Should be OK.&lt;/p&gt;

&lt;p&gt;I like quick and easy solutions for complex problems. We analyzed the build output using simple tools - shell and excel. Let's do it step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;extract the timestamps when cleanup DB starts:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;buildoutput.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"DefaultPrepAndExpectedTestCase - cleanupData"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/\([0123456789T.:-]*\).*DefaultPrepAndExpectedTestCase.*/\1/g'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cleanup_start.out
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;(&lt;code&gt;grep&lt;/code&gt; for the lines matching the substring, then using &lt;code&gt;sed&lt;/code&gt; to only output the timestamp)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;extract the timestamps of the next messages - this will roughly be the end time of the cleanups:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;buildoutput.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"DefaultPrepAndExpectedTestCase - cleanupData"&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\-\-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"DefaultPrepAndExpectedTestCase"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/\([0123456789T.:-]*\).*/\1/g'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cleanup_end.out
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;(&lt;code&gt;grep&lt;/code&gt; for the lines matching the substring and 1 line after, &lt;code&gt;grep&lt;/code&gt; out the first line and the line with '--', then using &lt;code&gt;sed&lt;/code&gt; to only output the timestamp)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Load both files to Excel&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choose &lt;em&gt;Data&lt;/em&gt; -&amp;gt; &lt;em&gt;From Text/CSV&lt;/em&gt; (Alt+A, FT)&lt;/li&gt;
&lt;li&gt;select the first file &lt;code&gt;cleanup_start.out&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Source&lt;/em&gt; and &lt;em&gt;Change type&lt;/em&gt; steps should be added automatically, add a few more:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZD9bTzxK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bb9rp4cpcyhr0byvdk9o.png" alt="Power Query Editor"&gt;
&lt;/li&gt;
&lt;li&gt;the new &lt;em&gt;Added column&lt;/em&gt; contains &lt;code&gt;Time.Hour([Column1])*60*60+Time.Minute([Column1])*60+Time.Second([Column1])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;Removed columns&lt;/em&gt; step just deletes the &lt;em&gt;Column1&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;now the excel contains the amount of seconds (incl. fraction) elapsed from the day start&lt;/li&gt;
&lt;li&gt;repeat the same for second file &lt;code&gt;cleanup_end.out&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add diff column
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JV4vMmT1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/oumc3cini5cxlm7kp061.png" alt="Diff column"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the diff column contains roughly the duration of the DB cleanup. I simply put a sum at the end of the column to calculate the 1800 seconds mentioned above. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;plot a chart&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;select the whole third column&lt;/li&gt;
&lt;li&gt;add a new line chart
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r6K_74h5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pw4zm0wqf83vj9b6kj77.png" alt="Chart"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The chart now shows how log it took the DB cleanup during the build. You can see important information here. It was all fine and then we see sudden blocks of peaks. The pattern looks very suspicious. &lt;/p&gt;

&lt;p&gt;Short stare at the chart, some experience and the 'blink' moment - the &lt;strong&gt;Garbage Collector&lt;/strong&gt;!!!  This explains why it takes 7 seconds to cleanup the DB. Because the full GC runs in the background.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Improving the situation
&lt;/h1&gt;

&lt;p&gt;We checked the POM of the affected module and ... guess the surefire plugin configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-surefire-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${surefire.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;argLine&amp;gt;&lt;/span&gt;${surefireArgLine} -Dfile.encoding=UTF-8 -Xmx1024m
            &lt;span class="nt"&gt;&amp;lt;/argLine&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Yep, the -Xmx1024m. The maximum Java heap space explicitly set to 1024m. This is not enough for out tests any longer. Even though we set the Xmx in the build pipeline, it is not picked up. This explains everything - when the heap space is reaching the maximum, the full GC is invoked to free up some memory. This radically slows down the test execution. &lt;/p&gt;

&lt;p&gt;First thing we did - increase the Xmx to at least 2048m (or leave it empty to default to 1/4 of RAM, whichever is smaller).&lt;/p&gt;

&lt;p&gt;Since we are here, let me show you one more thing. If your tests are good enough to run in isolation properly, and you have the HW that is powerful enough, play around with this configuration of surefire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;forkCount&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/forkCount&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;parallel&amp;gt;&lt;/span&gt;classes&lt;span class="nt"&gt;&amp;lt;/parallel&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;threadCountClasses&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/threadCountClasses&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This settings means, that there will be 2 forks of surefire booter (this guy actually executes the tests in isolated JVM), it will use 2 threads to run the tests and it will break down the test suite to classes to distribute them over. Important to remember here is that the &lt;code&gt;threadCountClasses&lt;/code&gt; is used within the same (forked) JVM, meaning that there will be 2 threads per fork. So if you run into race conditions, just decrease the &lt;code&gt;threadCountClasses&lt;/code&gt; to 1. The tests will still run in parallel, but in 1 thread per fork. And because H2 is typically 1 per JVM, you should be fine. Unless you have another kind shared data outside JVM.&lt;/p&gt;

&lt;p&gt;The outcome that we reached - down to &lt;strong&gt;11 minutes&lt;/strong&gt; build time from 25 minutes, which is only 44% of previous duration.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Understand the tools that you use (e.g. MockMvc, DBUnit, Spring Test DBUnit, Surefire plugin). Analyse your build outputs. Do not close the case with just stating that your application grows, your test suite grows, hence the build time gets longer. Sure this is true, but you should always reserve some time for refactoring and improving your code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Appendix
&lt;/h1&gt;

&lt;h2&gt;
  
  
  DBUnit H2 &lt;code&gt;TRUNCATE_TABLE&lt;/code&gt; operation
&lt;/h2&gt;

&lt;p&gt;Truncate table is usually faster operation than delete from. There are some catches though. Read more about it &lt;a href="https://stackoverflow.com/questions/139630/whats-the-difference-between-truncate-and-delete-in-sql"&gt;here&lt;/a&gt;. H2 won't allow you to truncate table if there are foreign keys to that table. However, there is a special H2 syntax that you can make use of. We implemented the following DBUnit operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;H2TruncateOperation&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dbunit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AbstractOperation&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;H2TruncateOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IDatabaseConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IDataSet&lt;/span&gt; &lt;span class="n"&gt;dataSet&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;DatabaseUnitException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execute(connection={}, dataSet={}) - start"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataSet&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;IDataSet&lt;/span&gt; &lt;span class="n"&gt;databaseDataSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createDataSet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;DatabaseConfig&lt;/span&gt; &lt;span class="n"&gt;databaseConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConfig&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;IStatementFactory&lt;/span&gt; &lt;span class="n"&gt;statementFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IStatementFactory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;databaseConfig&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://www.dbunit.org/properties/statementFactory"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;IBatchStatement&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statementFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createBatchStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tableNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tablesSeen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ITableIterator&lt;/span&gt; &lt;span class="n"&gt;iterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTableMetaData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getTableName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;tablesSeen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;tableNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;tablesSeen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;tableNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SET FOREIGN_KEY_CHECKS=0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;(;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;tableNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tableNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="nc"&gt;ITableMetaData&lt;/span&gt; &lt;span class="n"&gt;databaseMetaData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;databaseDataSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTableMetaData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;databaseMetaData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTableName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TRUNCATE TABLE "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                             &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQualifiedName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSchema&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                                   &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                             &lt;span class="s"&gt;" RESTART IDENTITY"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDebugEnabled&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Added SQL: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SET FOREIGN_KEY_CHECKS=1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeBatch&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clearBatch&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;An example SQL statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;FOREIGN_KEY_CHECKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;TRUNCATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;table1&lt;/span&gt; &lt;span class="k"&gt;RESTART&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;TRUNCATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;table2&lt;/span&gt; &lt;span class="k"&gt;RESTART&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;FOREIGN_KEY_CHECKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can use pass this operation to plain DB unit configuration like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AbstractDatabaseTester&lt;/span&gt; &lt;span class="n"&gt;databaseTester&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DataSourceDatabaseTester&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;databaseTester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTearDownOperation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;H2TruncateOperation&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;or implement a new DB Operation lookup if you are using Spring Test DBUnit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;H2SpecificDatabaseOperationLookup&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DefaultDatabaseOperationLookup&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dbunit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DatabaseOperation&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DatabaseOperation&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;DatabaseOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TRUNCATE_TABLE&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;
               &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;H2TruncateOperation&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;
               &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and use annotations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@DbUnitConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;databaseOperationLookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;H2SpecificDatabaseOperationLookup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>java</category>
      <category>spring</category>
      <category>unittest</category>
      <category>devops</category>
    </item>
    <item>
      <title>Zorin OS for a web developer? Yes please!</title>
      <dc:creator>Vladimir Nemergut</dc:creator>
      <pubDate>Fri, 10 Jan 2020 16:41:12 +0000</pubDate>
      <link>https://dev.to/vladonemo/zorin-os-for-a-web-developer-yes-please-3io3</link>
      <guid>https://dev.to/vladonemo/zorin-os-for-a-web-developer-yes-please-3io3</guid>
      <description>&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;I've been a developer for many years. My beginnings were on Windows 95. Since then my primary development environment has been Windows OS based.&lt;/p&gt;

&lt;p&gt;Some time back I had an opportunity to switch my work web development environment to MacOS. MacOS is great for that. Switching back to Windows was not necessarilly any comparable experience 😄&lt;/p&gt;

&lt;p&gt;So what about trying something else, shall we?&lt;/p&gt;

&lt;p&gt;In this chapter you get some introduction and I share my feelings and struggles.&lt;/p&gt;

&lt;h1&gt;
  
  
  Zorin OS?
&lt;/h1&gt;

&lt;p&gt;When searching over a dozen of modern Linux distributions, I suddenly found Zorin OS hidden behind Ubuntu, Debian, Mint (from a popularity perspective) and elementary OS (from UX perspective). It's great looking desktop OS. Very visually appealing. There are a few editions you might choose from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ultimate&lt;/li&gt;
&lt;li&gt;Core&lt;/li&gt;
&lt;li&gt;Lite&lt;/li&gt;
&lt;li&gt;Education and Education Lite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All but Ultimate are free of charge.&lt;/p&gt;

&lt;p&gt;I've got a spare Fujitsu Celsius H710 laptop, so I decided to install Core edition on it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;

&lt;p&gt;I don't want to go through the whole installation procedure. You can find many tutorials on internet. After burning the installation DVD of Zorin OS 15.1, I just followed the installation steps. It is super easy (if you manage to boot from DVD).&lt;/p&gt;

&lt;h1&gt;
  
  
  Struggle with NVidia drivers
&lt;/h1&gt;

&lt;p&gt;The first installation went through, all fine. Then I wanted to tweak the OS a little bit, that was a mistake 😄. The H710 has hybrid graphics. So actually 2 graphics adapters - Intel and NVidia. I tried to install the additional NVidia drivers from the &lt;em&gt;Software &amp;amp; Updates&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5mkrmuh2szrdv419c0ha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5mkrmuh2szrdv419c0ha.png" alt="Installing additional drivers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neither of the NVidia drivers worked perfectly. With the proprietary driver you get this annoying NVidia logo while booting and the booting seemed to take longer. Both proprietary and open source NVidia drivers caused the brightness adjustment to stop working. I mean the functional key still work, but the brightness always stays on 100%. Switching back to Nouveau drivers was a nightmare, too. The screen resolution went to 640x480 and no way to change it properly. &lt;/p&gt;

&lt;p&gt;If you now get a cool idea to switch to Intel graphics adapter ...&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

sudo prime-select intel


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;... don't do this 😏. At least I was not able to make it work with Intel adapter. Mind you, even reinstall won't help as you need to switch back to NVidia.&lt;/p&gt;

&lt;p&gt;The only remedy that worked for me was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;switch to NVidia
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;sudo prime-select nvidia&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- uninstall NVidia drivers completely:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sudo apt purge nvidia-*&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
After that Zorin OS experience was back to the top level:
- Zorin OS logo when booting in high-res
- resolution back to screen native

# Laptop power management

Power management is surely important if you want to use your laptop on battery. The most advanced tool is the [TLP](https://linrunner.de/en/tlp/tlp.html). 

Just install TLP on your laptop:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sudo add-apt-repository ppa:linrunner/tlp&lt;br&gt;
sudo apt update&lt;br&gt;
sudo apt install tlp tlp-rdw&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
You might find TLP GUI handy:
``
sudo add-apt-repository ppa:linuxuprising/apps
sudo apt update
sudo apt install tlpui
```

![TLP GUI](https://thepracticaldev.s3.amazonaws.com/i/sjfa7zk099tx0kqocg2u.png)

Then go to Software app and search for TLP Extension. This will give you an easy way to switch between power management profiles without opening a terminal window and typing in something.

![TLP Extension](https://thepracticaldev.s3.amazonaws.com/i/bo9v0905fnxxh4bxy84n.png)

The TLP installs the default TLP profile under `/etc/defaults/tlp`. You might follow these steps to create your custom profiles.

- copy the `/etc/defaults/tlp` to `~/.tlp/&amp;lt;Your profile name&amp;gt;` as many times as many profiles you want
- open the respective profile file in TLP GUI
- make all modifications to your liking and save it

# What next?

In the next chapters I'd share my impressions of using Zorin OS as web developer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>linux</category>
      <category>webdev</category>
      <category>zorinos</category>
    </item>
  </channel>
</rss>
