All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.stuff.spring.SpringSQLSchemaUpdater Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2022 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.stuff.spring;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Comparator;

import org.dellroad.stuff.schema.DatabaseAction;
import org.dellroad.stuff.schema.SQLCommand;
import org.dellroad.stuff.schema.SQLSchemaUpdater;
import org.dellroad.stuff.schema.SchemaUpdate;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;

/**
 * {@link SQLSchemaUpdater} optimized for use with Spring.
 *
 * 
 * 
 * 
 *
 * 
    *
  • {@link #apply(Connection, DatabaseAction) apply()} is overridden so Spring {@link DataAccessException}s are thrown.
  • *
  • {@link #indicatesUninitializedDatabase indicatesUninitializedDatabase()} is overridden to examine exceptions * and more precisely using Spring's exception translation infrastructure to filter out false positives.
  • *
  • {@link #getOrderingTieBreaker} is overridden to break ties by ordering updates in the same order * as they are defined in the bean factory.
  • *
  • This class implements {@link InitializingBean} and verifies all required properties are set.
  • *
  • If no updates are {@linkplain #setUpdates explicitly configured}, then all {@link SpringSQLSchemaUpdate}s found * in the containing bean factory are automatically configured. *
* *

* An example of how this class can be combined with custom XML to define an updater, all its updates, * and a {@link org.dellroad.stuff.schema.SchemaUpdatingDataSource} that automatically updates the database schema: *


 *  <beans xmlns="http://www.springframework.org/schema/beans"
 *    xmlns:dellroad-stuff="http://dellroad-stuff.googlecode.com/schema/dellroad-stuff"
 *    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 *    xmlns:p="http://www.springframework.org/schema/p"
 *    xsi:schemaLocation="
 *      http://www.springframework.org/schema/beans
 *        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 *      http://dellroad-stuff.googlecode.com/schema/dellroad-stuff
 *        http://dellroad-stuff.googlecode.com/svn/wiki/schemas/dellroad-stuff-1.0.xsd">
 *
 *     <!-- DataSource that automatically updates the database schema -->
 *     <bean id="dataSource" class="org.dellroad.stuff.schema.SchemaUpdatingDataSource"
 *       p:dataSource-ref="realDataSource" p:schemaUpdater-ref="schemaUpdater"/>
 *
 *     <!--
 *          Database updater bean. This is used on first access to the DataSource above. Notes:
 *            - "databaseInitialization" is used to initialize the schema (first time only)
 *            - "updateTableInitialization" is used to initialize the update table (first time only)
 *            - In this example, we just use dellroad-stuff's update table initialization for MySQL
 *            - The <dellroad-stuff:sql-update> beans below will be auto-detected
 *     -->
 *     <bean id="schemaUpdater" class="org.dellroad.stuff.spring.SpringSQLSchemaUpdater">
 *         <property name="databaseInitialization">
 *             <dellroad-stuff:sql resource="classpath:databaseInit.sql"/>
 *         </property>
 *         <property name="updateTableInitialization">
 *             <dellroad-stuff:sql resource="classpath:org/dellroad/stuff/schema/updateTable-mysql.sql"/>
 *         </property>
 *     </bean>
 *
 *      <!-- Schema update to add the 'phone' column to the 'User' table -->
 *      <dellroad-stuff:sql-update id="addPhone">ALTER TABLE User ADD phone VARCHAR(64)</dellroad-stuff:sql-update>
 *
 *      <!-- Schema update to run some complicated external SQL script -->
 *      <dellroad-stuff:sql-update id="majorChanges" depends-on="addPhone" resource="classpath:majorChanges.sql"/>
 *
 *      <!-- Multiple SQL commands that will be automatically separated into distinct updates -->
 *      <dellroad-stuff:sql-update id="renameColumn">
 *          ALTER TABLE User ADD newName VARCHAR(64);
 *          ALTER TABLE User SET newName = oldName;
 *          ALTER TABLE User DROP oldName;
 *      </dellroad-stuff:sql-update>
 *
 *      <!-- Add more schema updates over time as needed and everything just works... -->
 *
 *  </beans>
 * 
* *

* In the case no schema updates are explicitly configured, it is required that this updater and all of its * schema updates are defined in the same {@link ListableBeanFactory}. */ public class SpringSQLSchemaUpdater extends SQLSchemaUpdater implements BeanFactoryAware, InitializingBean { private ListableBeanFactory beanFactory; @Override public void afterPropertiesSet() throws Exception { if (this.getDatabaseInitialization() == null) throw new Exception("no database initialization configured"); if (this.getUpdateTableInitialization() == null) throw new Exception("no update table initialization configured"); if (this.getUpdates() == null) { if (this.beanFactory == null) { throw new IllegalArgumentException("no updates explicitly configured and the containing BeanFactory" + " is not a ListableBeanFactory: " + this.beanFactory); } this.setUpdates(this.beanFactory.getBeansOfType(SpringSQLSchemaUpdate.class).values()); } } @Override public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ListableBeanFactory) this.beanFactory = (ListableBeanFactory)beanFactory; } /** * Determine if an exception thrown during {@link #databaseNeedsInitialization} is consistent with * an uninitialized database. * *

* The implementation in {@link SpringSQLSchemaUpdater} looks for a {@link BadSqlGrammarException}. */ @Override protected boolean indicatesUninitializedDatabase(Connection c, SQLException e) throws SQLException { return this.translate(e, c, null) instanceof BadSqlGrammarException; } /** * Apply a {@link DatabaseAction} to a {@link Connection}. * *

* The implementation in {@link SQLSchemaUpdater} invokes the action and delegates to * {@link #translate(SQLException, Connection, String) translate()} to convert any {@link SQLException} thrown. * * @throws SQLException if an error occurs attempting to translate a thrown SQLException * @throws DataAccessException if an error occurs accessing the database * @see #translate(SQLException, Connection, String) translate() */ @Override protected void apply(Connection c, DatabaseAction action) throws SQLException { try { super.apply(c, action); } catch (SQLException e) { String sql = action instanceof SQLCommand ? ((SQLCommand)action).getSQL() : null; throw this.translate(e, c, sql); } } /** * Converts {@link SQLException}s into Spring {@link DataAccessException}s. * * @param e original exception * @param c the connection on which the exception coccurred * @param sql the SQL statement that generated the exception * @return the corresponding Spring {@link DataAccessException} * @throws SQLException if exception translation fails */ protected DataAccessException translate(SQLException e, Connection c, String sql) throws SQLException { return new SQLErrorCodeSQLExceptionTranslator(c.getMetaData().getDatabaseProductName()) .translate("database access during schema update", sql, e); } /** * Get the preferred ordering of two updates that do not have any predecessor constraints * (including implied indirect constraints) between them. * *

* In the case no schema updates are explicitly configured, the {@link Comparator} returned by the * implementation in {@link SpringSQLSchemaUpdater} sorts updates in the same order that they appear * in the containing {@link ListableBeanFactory}. Otherwise, the * {@linkplain org.dellroad.stuff.schema.AbstractSchemaUpdater#getOrderingTieBreaker superclass method} is used. */ @Override protected Comparator> getOrderingTieBreaker() { if (this.beanFactory == null) return super.getOrderingTieBreaker(); final BeanNameComparator beanNameComparator = new BeanNameComparator(this.beanFactory); return new Comparator>() { @Override public int compare(SchemaUpdate update1, SchemaUpdate update2) { return beanNameComparator.compare(update1.getName(), update2.getName()); } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy