<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>agimatec &#187; Database</title>
	<atom:link href="http://www.agimatec.de/blog/tag/database/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.agimatec.de/blog</link>
	<description>Clash of realities</description>
	<lastBuildDate>Tue, 22 Dec 2009 16:50:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Auditing and entity versioning based on triggers and hibernate</title>
		<link>http://www.agimatec.de/blog/2009/07/auditing-and-entity-versioning-based-on-triggers-and-hibernate/</link>
		<comments>http://www.agimatec.de/blog/2009/07/auditing-and-entity-versioning-based-on-triggers-and-hibernate/#comments</comments>
		<pubDate>Fri, 03 Jul 2009 13:11:20 +0000</pubDate>
		<dc:creator>roman.stumm</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://www.agimatec.de/blog/?p=644</guid>
		<description><![CDATA[If you are looking for a framework for auditing the versions and changes of your hibernate objects, you might probably be satisfied with Envers (http://www.jboss.org/envers/), a solution based on hibernate. In this blog entry I want to give you an impression of an alternative solution by agimatec, that is slightly different. It is the solution [...]]]></description>
			<content:encoded><![CDATA[<p>If you are looking for a framework for auditing the versions and changes of your hibernate objects, you might probably be satisfied with <em><strong>Envers</strong></em> (http://www.jboss.org/envers/), a solution based on hibernate.</p>
<p>In this blog entry I want to give you an impression of an <strong><em>alternative solution</em><em> by agimatec</em></strong>, that is slightly different. It is the solution we are using in our products for more than two years and with which we are satisfied. It would be interesting to hear your options on this.</p>
<p><strong>The concept</strong></p>
<p><em>Database:</em><strong><br />
</strong></p>
<p>We generate history tables for each table for which we need auditing. We generate database triggers (oracle or postgres) to automatically insert data in the history tables when entities are stored/deleted.</p>
<p>We do not store duplicate data, which means that our history tables contain only OLD data. All current data is ONLY stored in the application&#8217;s tables (usually mapped by hibernate/Ejb3).</p>
<p><em>Configuration:</em></p>
<p>We have a XML configuration, that contains the table names for which we want to enable our history solution. This configuration lets you specify:</p>
<ul>
<li>name of history table (or a default name will be used)</li>
<li>which columns to exclude from history (default is that all columns are historised)</li>
<li>optionally you can turn off history for insert and/or update, change trigger names etc.</li>
</ul>
<p>The XML configuration controls the tables and triggers generated by freemarker templates.</p>
<p><em>Features:</em></p>
<p>It is not only important to version the changes, but also to store, the timestamp of a change and some context information about the change (e.g. who is the actor, which application, from which sessionID etc.). Any context information can be associated with a version record by storing a contextID in the history table&#8217;s rows.</p>
<p>This is also a way to separate changes from user-specific data, which might be a judical requirement. An extended Hibernate event handler stores the contextID into the database session by calling a stored procedure, so that the history triggers and access the contextID to store it together with the history data.</p>
<p>Each history table has additional columns:</p>
<ul>
<li>HIST_TIME (timestamp of change)</li>
<li>HIST_CONTEXTID (ID of context providing additional information)</li>
<li>HIST_TYPE (to distinguish INSERT, UPDATE and DELETION)</li>
</ul>
<p>A simple java API exists to query historical data. It returns the same classes as you use for your hibernate entities, but it materializes the objects itself. Hibernate does not know anything about the history tables or mappings.</p>
<p><strong>The benefits</strong></p>
<p>There are some differences between the solution provided by Envers and agimatec:</p>
<ul>
<li>Envers needs a global _revision table, agimatec&#8217;s history rows are identified by the primary key and a version number (@Version) incremented from 1 for each entity/no global _revision</li>
<li>You can change data with any SQL tool.Historisation does not rely on hibernate as the only API to change the database.</li>
<li>Whenever you increment a row&#8217;s version, the history triggers will automatically write historical data. By this way, you can use migration scripts that can control whether history data will be stored.</li>
<li>Envers stores current data in history tables, which makes it a bit easier to query the data, but with means redundancy (and potential data inconsistencies).</li>
<li>Both framework are integrated with Hibernate by extending the Hibernate Events (to provide context information).</li>
</ul>
<p><strong>Examples</strong></p>
<p><em>History configuration</em><br />
<code><br />
&lt;historyConfig&gt;<br />
&lt;tableConfig&gt;<br />
&lt;tableName&gt;booking&lt;/tableName&gt;<br />
&lt;/tableConfig&gt;<br />
&lt;tableConfig&gt;<br />
&lt;tableName&gt;customer&lt;/tableName&gt;<br />
&lt;historyTable&gt;h_cust&lt;/historyTable&gt;<br />
&lt;insertTrigger&gt;TR_I_cust&lt;/insertTrigger&gt;<br />
&lt;updateTrigger&gt;TR_U_cust&lt;/updateTrigger&gt;<br />
&lt;/tableConfig&gt;<br />
</code></p>
<p><em>Generated tables</em><br />
<code><br />
CREATE TABLE H_JOURNAL (<br />
ID INTEGER NOT NULL,<br />
HIST_TABLE VARCHAR(50) NOT NULL,<br />
HIST_TIME TIMESTAMP NOT NULL,<br />
HIST_CONTEXTID VARCHAR(40),<br />
CONSTRAINT H_JOURNAL_PK PRIMARY KEY (ID, HIST_TABLE)<br />
);</code></p>
<p><code>CREATE TABLE H_booking (<br />
VERSION INTEGER NOT NULL,<br />
booking_id INTEGER NOT NULL,<br />
price NUMBER,<br />
HIST_TIME TIMESTAMP NOT NULL,<br />
HIST_CONTEXTID VARCHAR(40),<br />
HIST_TYPE CHAR(1) NOT NULL,<br />
CONSTRAINT H_booking_PK PRIMARY KEY (VERSION, booking_id)<br />
);<br />
CREATE TABLE H_cust (<br />
VERSION INTEGER NOT NULL,<br />
cust_id INTEGER NOT NULL,<br />
first_name VARCHAR2(40),<br />
last_name VARCHAR2(40),<br />
HIST_TIME TIMESTAMP NOT NULL,<br />
HIST_CONTEXTID VARCHAR(40),<br />
HIST_TYPE CHAR(1) NOT NULL,<br />
CONSTRAINT H_cust_PK PRIMARY KEY (VERSION, card_id)<br />
);</code></p>
<p><em>Generated triggers</em><br />
<code><br />
CREATE OR REPLACE TRIGGER TR_I_cust<br />
AFTER INSERT ON customer<br />
REFERENCING NEW AS NEW<br />
FOR EACH ROW<br />
BEGIN<br />
setTATime();<br />
INSERT INTO H_JOURNAL (ID,  HIST_TIME, HIST_CONTEXTID, HIST_TABLE)<br />
select :NEW.card_id, h.ts, h_session.getContextId, 'customer' from h_tatime h;<br />
END;<br />
/</code><br />
<code><br />
CREATE OR REPLACE TRIGGER TR_U_cust<br />
AFTER DELETE OR UPDATE<br />
ON customer<br />
REFERENCING NEW AS NEW OLD AS OLD<br />
FOR EACH ROW<br />
BEGIN<br />
IF(DELETING) THEN<br />
setTATime();<br />
INSERT INTO H_cust (<br />
cust_id,<br />
version,<br />
first_name,<br />
last_name,<br />
HIST_TIME, HIST_CONTEXTID, HIST_TYPE)<br />
select<br />
:OLD.cust_id,<br />
:OLD.version,<br />
:OLD.first_name,<br />
:OLD.last_name,<br />
h.ts, h_session.getContextId, 'D' from h_tatime h;<br />
ELSIF (UPDATING AND :OLD.VERSION != :NEW.VERSION) THEN<br />
setTATime();<br />
INSERT INTO H_cust (<br />
cust_id,<br />
version,<br />
first_name,<br />
last_name,<br />
HIST_TIME, HIST_CONTEXTID, HIST_TYPE)<br />
select<br />
:OLD.cust_id,<br />
:OLD.version,<br />
:OLD.first_name,<br />
:OLD.last_name,<br />
h.ts, h_session.getContextId, 'U' from h_tatime h;<br />
END IF;<br />
END;<br />
/</code></p>
<p><strong>Java API</strong></p>
<p>The implementation of class <strong><em>HibernateHistoryReader</em></strong> was rather complex, because of the need to resolve the relationships through the current tables and the history tables. All changes done inside a single transaction are joined together by using a timestamp in HIST_TIME that is unique inside the running database transaction.</p>
<p>Here is interface <strong><em>HistoryReader</em></strong>:</p>
<p><code>interface HistoryReader {</code><br />
<code><br />
void open(EntityManager entityManager);<br />
void close();</code><br />
<code><br />
List&lt;EntityRevision&gt; getRevisions(Class entityClass, Object primaryKey, Timeframe timeframe);<br />
EntityRevision getRevision(Class entityClass, Object primaryKey, int version);</code></p>
<p><code>Map&lt;Object, List&lt;EntityRevision&gt;&gt; getChildrenRevisions(Class parentEntityClass,EntityRevision parentRevision,String relationship);</code></p>
<p><code>&lt;E&gt; HistoryEntity&lt;E&gt; loadEntity(Class&lt;E&gt; entityClass, Object primaryKey,Timestamp time);<br />
&lt;E&gt; List&lt;HistoryEntity&lt;E&gt;&gt; loadChildren(Class parentEntityClass, String relationship,<br />
Object parentPrimaryKey, Timestamp time);</code></p>
<p><code>}</code></p>
<p>Where class HistoryEntity contains the entity instance for a given point of time and some additional information, such as the contextID and the foreignKey values to load some to-one-references.</p>
<p>If you are interested in more details contact us.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.agimatec.de/blog/2009/07/auditing-and-entity-versioning-based-on-triggers-and-hibernate/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>I love Oracle!</title>
		<link>http://www.agimatec.de/blog/2009/02/i-love-oracle/</link>
		<comments>http://www.agimatec.de/blog/2009/02/i-love-oracle/#comments</comments>
		<pubDate>Thu, 05 Feb 2009 16:11:31 +0000</pubDate>
		<dc:creator>roman.stumm</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Database]]></category>

		<guid isPermaLink="false">http://www.agimatec.de/blog/?p=516</guid>
		<description><![CDATA[I execute this SQL with an Oracle 10g database with the latest JDBC driver (Oracle Thin 10.2.0.4). Can you guess the difference between select * from dual d where  d.dummy &#60;= &#8216;X&#8217; /* {} */ and select * from dual d where  d.dummy &#60;= &#8216;X&#8217; ? Just a comment? - Well, the surprise is, that [...]]]></description>
			<content:encoded><![CDATA[<p>I execute this SQL with an Oracle 10g database with the latest JDBC driver (Oracle Thin 10.2.0.4).</p>
<p>Can you guess the difference between</p>
<blockquote><p>select * from dual d where  d.dummy &lt;= &#8216;X&#8217; /* {} */</p></blockquote>
<p>and</p>
<blockquote><p>select * from dual d where  d.dummy &lt;= &#8216;X&#8217;</p></blockquote>
<p>?</p>
<p>Just a comment? -</p>
<p>Well, the surprise is, that the driver throws</p>
<p>java.lang.NullPointerException  at oracle.jdbc.driver.T4C8Oall.getNumRows(T4C8Oall.java:876)</p>
<p>in the first case that contains the comment and executes the statement correctly in the second case.</p>
<p>Thanks, Oracle!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.agimatec.de/blog/2009/02/i-love-oracle/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>&#8220;insert-or-update&#8221;: locking pitfalls with EJB3</title>
		<link>http://www.agimatec.de/blog/2008/08/insert-or-update-locking-pitfalls-with-ejb3/</link>
		<comments>http://www.agimatec.de/blog/2008/08/insert-or-update-locking-pitfalls-with-ejb3/#comments</comments>
		<pubDate>Fri, 29 Aug 2008 16:30:32 +0000</pubDate>
		<dc:creator>roman.stumm</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[EJB3]]></category>

		<guid isPermaLink="false">http://www.agimatec.de/blog/?p=319</guid>
		<description><![CDATA[The problem sounds like something very common: a server-side program in a multi-threaded environment has to save an entity. It must decide either to create a new instance or to load an existing instance and update it. I will show you some examples when using EJB3-entities (with Hibernate, tested with Oracle and Postgres). We could [...]]]></description>
			<content:encoded><![CDATA[<p>The problem sounds like something very common: a server-side program in a multi-threaded environment has to save an entity. It must decide either to create a new instance or to load an existing instance and update it. I will show you some examples when using EJB3-entities (with Hibernate, tested with Oracle and Postgres).</p>
<p>We could call merge(): ["MyEntity" is the name for a business entity, just for the examples.]</p>
<p><strong>Example 1:</strong></p>
<pre name="code" class="java">MyEntity persistentEntity = entityManager.merge(detachedEntity);</pre>
<p>or do it the explicit way, when some more business logic is required before we save:</p>
<p><strong>Example 2:</strong></p>
<pre name="code" class="java">  MyEntity persistentEntity = entityManager.find(MyEntity.class, detachedEntity.getId());
  boolean isNew = false;
  if(persistentEntity == null) {
    persistentEntity = new MyEntity();
    isNew = true;
  }
  // ... some business logic here that works with detachedEntity and persistentEntity
  if(isNew) {
    entityManager.persist(persistentEntity);
  } // otherwise update happens implicitly</pre>
<p><strong>But: This does not solve the concurrency problems we get when two transactions are doing the same thing at (nearly) the same time! </strong></p>
<p>What could happen is, that both transactions try to load the object first where find() returns null, because the object is not stored yet. Thus both transactions will try to insert the entity, which would create the entity twice in the database or would cause one of the transactions to fail (e.g. because of a constraint violation).</p>
<p>You do not need much code to solve this situation, but be careful, there are some pitfalls!</p>
<p><span id="more-319"></span></p>
<p><strong>There is even a concurrency problem when the entity is already in the database: </strong></p>
<p>When both transactions change the entity concurrently, this will result in lost updates or an OptimisticConcurrencyException. &#8211; I don&#8217;t like any of them!</p>
<p>The program must load and lock the entity (select-for-update), so that each subsequent transaction waits on the lock until the first transaction releases the lock (when the transaction ends with commit or rollback).</p>
<p>The simplest thing is to call</p>
<pre name="code" class="java">entityManager.lock(persistentEntity, LockModeType.READ);</pre>
<p>but:</p>
<ul>
<li>according to EJB3-specifiation, lock() is only supported for entities with a @version attribute!<br />
The @version attribute normally is used for optimistic concurrency and I got bad surprises calling lock(): sometimes I got a StaleObjectStateException instead of a locked object!<br />
(Hibernate also supports lock() for entities without a @version attribute.)</li>
</ul>
<ul>
<li>when the persistentEntity has not been inserted, there is no object to be locked!<br />
Lock() requires that the object has been saved before.</li>
</ul>
<p><strong>Let me suggest a solution:</strong></p>
<p>I created an entity named &#8220;LockEntry&#8221;, that allows me to implement global locks.</p>
<pre name="code" class="java">@Entity
public class LockEntry {
  private String id;
  private int version;

  @Id public String getId() { return id; }
  public void setId(String id) { this.id = id; }

  @Version public int getVersion() { return version; }
  public void setVersion(int version) { this.version = version; }
}</pre>
<p>Each kind of lock gets its own name and is inserted in LockEntry&#8217;s table when the database is created.</p>
<pre name="code"  class="java">insert into Lockentry (id, version) values ('MyEntity', 0);</pre>
<p>The algorithms of example (1) and (2) just start with two additional statements:</p>
<pre name="code" class="java">LockEntry entry = entityManager.getReference(LockEntry.class, "MyEntity");
entityManager.lock(entry, LockModeType.READ);</pre>
<p>This places a lock on LockEntity with id=&#8221;MyEntity&#8221; to ensure that the transaction will not cause conflicts or lost updates.</p>
<p><strong>But you must notice one more thing:</strong></p>
<p>When working with different entityManagers (e.g. when using multiple databases or database schemas), the LockEntry entity must be locked with the same entityManager than your business entity (MyEntity)! Otherwise you can get lost updates or concurrency conflicts, because during the commit of the transaction the lock on &#8220;LockEntry&#8221; must be released in the same database transaction, in which the changes on MyEntity become visible to other transactions. (I am normally not using a two-phase-commit transaction manager.)</p>
<p><strong>Optimization:</strong></p>
<p>To improve throughput of concurrent transactions, you can decide to use the global lock only when you need to create the new entity:</p>
<pre name="code" class="java">MyEntity persistentEntity = loadAndLock(detachedEntity.getId());
boolean isNew = false;

if(persistentEntity == null) {
  LockEntry entry = entityManager.getReference(LockEntry.class, "MyEntity");
  entityManager.lock(entry, LockModeType.READ);
  persistentEntity = loadAndLock(detachedEntity.getId());
  if(persistentEntity == null) {
    persistentEntity = new MyEntity();
    isNew = true
  }
}

// ...</pre>
<p>Note that loadAndLock() is called twice &#8211; double checked locking is required here!</p>
<p>The reason for the loadAndLock() method is that because of Hibernate&#8217;s implementation of lock(), calling find() and lock() can result in a StaleObjectStateException:</p>
<pre name="code" class="java">MyEntiy loadAndLock(long id) {
  Query query = entityManager.createNativeQuery("select e.* from myentity e where id=?1 for update");
  query.setParameter(1, id);
  MyEntity found = null;
  try {
    found = query.getSingleResult();
  } catch(EntityNotFoundException e) {} catch(NoResultException e) {}
  // this might be neccessary, too:
  if(found != null) entityManager.refresh(found);
  return found;
}</pre>
<p>Weird, isn&#8217;t it? Or do you have a better solution?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.agimatec.de/blog/2008/08/insert-or-update-locking-pitfalls-with-ejb3/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Von neuer Software und alten Daten: dbmigrate</title>
		<link>http://www.agimatec.de/blog/2008/06/von-neuer-software-und-alten-daten/</link>
		<comments>http://www.agimatec.de/blog/2008/06/von-neuer-software-und-alten-daten/#comments</comments>
		<pubDate>Mon, 09 Jun 2008 13:02:44 +0000</pubDate>
		<dc:creator>roman.stumm</dc:creator>
				<category><![CDATA[Deutsch]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://www.agimatec.de/blog/?p=169</guid>
		<description><![CDATA[Anwendungen oder Produkte werden mitunter jahrelang weiterentwickelt, während diverse Versionen auf unterschiedlichen Test- oder Produktivsystemen bereits im Einsatz sind. Solange noch kein Release produktiv eingesetzt wird, sind Änderungen noch problemlos möglich. Sammeln sich in Datenbanken aber erst einmal Kundendaten, so müssen diese auch beim Deployment einer neuen Software erhalten bleiben. Und darum geht es heute: [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.agimatec.de/blog/wp-content/uploads/2008/06/dbmigrate.png"><img class="alignnone size-medium wp-image-170" title="dbmigrate" src="http://www.agimatec.de/blog/wp-content/uploads/2008/06/dbmigrate.png" alt="" width="219" height="292" /></a></p>
<p>Anwendungen oder Produkte werden mitunter jahrelang weiterentwickelt, während diverse Versionen auf unterschiedlichen Test- oder Produktivsystemen bereits  im Einsatz sind. Solange noch kein Release produktiv eingesetzt wird, sind Änderungen noch problemlos möglich. Sammeln sich in Datenbanken aber erst einmal Kundendaten, so müssen diese auch beim Deployment einer neuen Software erhalten bleiben. Und darum geht es heute: Datenbank-Installation und Schemaänderungen.</p>
<p><strong>Diese Probleme stellen sich in fast allen Projekten, die eine Datenbank einsetzen:</strong><span id="more-169"></span></p>
<p>1. Installation der Software incl. Erstellen einer Datenbank mit Initialdaten, die je nach Installationsparametern variieren können &#8211; am liebsten automatisch bereits in der Testphase.</p>
<p>2. Upgrade der Software, wobei die Datenbankstruktur (neue Tabellen, Spalten, Refactoring des Schemas) sich ändern muss und die vorhandenen Daten erhalten bleiben.</p>
<p>3. Unterschiedliche Test/Acceptance/Produktivsysteme diverser Kunden, die auf verschiedenen Versionsständen stehen müssen auf eine definierte Version &#8220;hoch&#8221;-migriert werden. Wer blickt da noch durch?</p>
<p>4. Die bange Frage nach jedem Deployment: Haben wir an der Datenbank nichts vergessen? (Ist der Index XY angelegt? Können wir die Spalte YZ jetzt endlich löschen? Sind wirklich alle Tabellen vorhanden und keine View oder Trigger ungültig durch die Änderungen am Datenbankschema?) Oftmals sind es sehr viele Kleinigkeiten, an die gedacht werden muss!</p>
<p>Muss eine Anwendung gleichzeitig Datenbank verschiedener Hersteller unterstützen, werden diese Probleme noch weiter verschärft. Auch die Anforderung, dass bestimmte Views oder Tabellen aus anderen Tabellen automatisch erzeugt werden können (z.B. Historytabellen), ist nicht ungewöhnlich.</p>
<p>Wir nutzen in unseren Projekte dazu seit Jahren das Tool &#8220;<a href="http://code.google.com/p/agimatec-tools/wiki/dbmigrate" target="_blank">dbmigrate</a>&#8220;, das wir seit Kurzem als OpenSource freigegeben haben.</p>
<p><strong>Was ist &#8220;dbmigrate&#8221;?</strong></p>
<ul>
<li>Ein OpenSource Framework zur Ausführung von SQL- und Groovy-Scripten. (Enthält auch einen einfachen SQL-Parser.)</li>
<li>Ein Tool, das mit Hilfe einer XML-Konfiguration das Setup bzw. die Migration von Datenbanken durchführen kann.</li>
</ul>
<p><strong>Welche Features sind enthalten?</strong></p>
<ul>
<li>Erforderliche SQL-Scripte werden anhand einer Namenskonvention automatisch ausgeführt.</li>
<li>Es können Verarbeitungsaktionen definiert werden, die immer vor oder nach der Migration laufen sollen.</li>
<li>Ausführung von Groovy-Scripten oder Aufruf beliebiger Java-Klassen ist problemlos möglich.</li>
<li>Bedingte Ausführung von Scripten oder Bedingungen innerhalb von Scripten erlauben eine Anpassung an die jeweilige Zielplattform.</li>
<li>Datenimporten aus Textdateien oder mit <a href="http://www.dbunit.org/" target="_blank">DBUnit </a>können eingebunden werden.</li>
<li>Prüfungen auf Vollständigkeit des Schemas und Konsistenz (Triggers, Views, Functions) durchführen.</li>
<li>Getestet mit Oracle und PostgresSQL, verwendbar für alle JDBC-Datenbanken.</li>
</ul>
<p><strong>Wie benutzt man &#8220;dbmigrate&#8221;?<br />
</strong></p>
<ol>
<li>Um &#8220;dbmigrate&#8221; einzusetzen sind Javakenntnisse nicht erforderlich, aber ggf. hilfreich.</li>
<li>Es muss daher kein Javaprojekt sein. &#8220;dbmigrate&#8221; kann für beliebige Datenbanken eingesetzt werden.</li>
<li>Um &#8220;dbmigrate&#8221; zu benutzen, muss eine Java-VM zur Verfügung stehen.</li>
<li>Die SQL-Scripte (CREATE TABLE, ALTER TABLE) müssen natürlich selbst geschrieben werden, oder werden mit einem anderen Tool (z.B.  einem ER-Modeller oder UML-Tool) erzeugt.</li>
</ol>
<p><strong>Konkret, bitte!</strong></p>
<ol>
<li>Ein <a href="http://agimatec-tools.googlecode.com/files/dbmigrate-example-dist-2.1.3.zip" target="_blank">Beispielprojekt runterladen</a>.</li>
<li>Zum Erzeugen der Datenbank aufrufen: &#8220;dbtool.bat setup.xml&#8221;</li>
<li>Zum Datenbank-Upgrade aufrufen: &#8220;dbtool.bat upgrade.xml&#8221;</li>
<li>Die <a href="http://code.google.com/p/agimatec-tools/wiki/dbmigrate" target="_blank">Dokumentation anschauen</a></li>
</ol>
<p><strong>Wie geht&#8217;s weiter?</strong></p>
<ol>
<li>Das Beispielprojekt so abwandeln, dass der eigene Jdbc-Driver und die SQL-Scripte zur Erzeugung der eigenen Datenbank verwendet werden.</li>
<li>In Zukunft bei Änderungen am Datenbankschema nur die Setup-Scripte (CREATE TABLE) anpassen.<br />
Damit kann eine neue Datenbank erzeugt werden.<br />
Außerdem dienen sie zum Soll-Ist-Vergleich um die Vollständigkeit der Datenbank zu prüfen (für Oracle und Postgres).</li>
<li>Die erforderlichen upgrade-Sql-Scripte (ALTER TABLE, UPDATE &#8230;) erstellen und unter entsprechendem Dateinamen im Scriptverzeichnis abspeichern. Der Aufruf des Tools führt nur die Scripte aus, die erforderlich sind.</li>
<li>Wir freuen uns über Feedback, Fragen und helfen gerne beim ersten eigenen &#8220;dbmigrate&#8221; Projekt.</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.agimatec.de/blog/2008/06/von-neuer-software-und-alten-daten/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
