Génération des scripts SQL-DDL

Dans cet article je présenterai des outils permettant la génération des scripts SQL de création/suppression et de remise à niveau des schémas des bases de données.

Ces outils se basent sur les outils SchemaUpdate et SchemaExport  faisant partie du projet "hibernate-core-4.3.4.Final".

JpaSchemaExport

JpaSchemaExport est une classe utilitaire permettant de générer les scripts sql de création et/ou suppression des schémas de la base de données.

@RunWith(SpringJUnit4ClassRunner.class) 
@Transactional 
// Chemin du fichier de configuration Spring 
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/beans.xml"}) 
public class JpaSchemaExport { 
 
   // Chemin de génération du script sql 
   private static final String DESTINATION_PATH = "src/test/resources/sqlcreatedrop.sql"; 
   // Chemin des fichiers d'environnement 
   private static final String ENV_PATH = "src/env/dev"; 
   // Formatter proprement le SQL généré 
   private static final boolean FORMAT = true; 
   // Si true génère seulement le script de suppression 
   private static final boolean JUST_DROP = false; 
   // Si true génère seulement le script de création 
   private static final boolean JUST_CREATE = false; 
   // Paramètres de connexion à la base de données, à personnaliser pour chaque projet 
   // utilisant cette classe 
   private static final String JDBC_DIALECT = "org.hibernate.dialect.SQLServer2005Dialect"; 
   private static final String JDBC_DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; 
   private static final String JDBC_URL = "jdbc:sqlserver://127.0.0.1:1433;databaseName=MEDIATHEQUE_DEV;shutdown=false"; 
   private static final String JDBC_USERNAME = "USER"; 
   private static final String JDBC_PASSWORD = "PASSWORD"; 

 
   @Autowired 
    LocalContainerEntityManagerFactoryBean EntityManagerFactory; 
 

static { 
        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); 
        Class<URLClassLoader> clazz = URLClassLoader.class; 
 
       // Use reflection 
       Method method; 
       try { 
            method = clazz.getDeclaredMethod("addURL", new Class[] {URL.class}); 
            method.setAccessible(true); 
            method.invoke(classLoader, new Object[] {new File(ENV_PATH).toURI().toURL()}); 
       } catch(Exception e) { 
            e.printStackTrace(); 
       } 
   } 
 
    

  @Test 
   public void generer_sql_script_diff() throws Exception { 
        execute(EntityManagerFactory, DESTINATION_PATH, FORMAT, JUST_CREATE, JUST_DROP); 
   } 
 
   /** 
     * Génère le script sql de création. 
     *  
     * @param EntityManagerFactory 
     * @param destination chemin de génération du script sql. 
     * @param propertiesPath fichier de paramètres de connexion à la base de données 
     * @param format si true le SQL généré sera formaté proprement 
     * @param justCreate si true génère seulement le script de création 
     * @param justDrop si true génère seulement le script de suppression 
     * @throws IOException 
     * @throws Exception 
     */ 
   @SuppressWarnings("unchecked") 
   protected void execute(LocalContainerEntityManagerFactoryBean EntityManagerFactory, String                                      destination, boolean format, boolean justCreate, boolean justDrop)  throws IOException { 
        List<Exception> exceptions = new ArrayList<Exception>(); 
       try { 
            Configuration hbmcfg = new Configuration(); 
           // Ajout de toutes les entités du projet à la configuration 
           for(Class<?> clazz : getEntityClass(EntityManagerFactory)) { 
                hbmcfg.addAnnotatedClass(clazz); 
           } 
            Properties props = new Properties(); 
            props.putAll(hbmcfg.getProperties()); 
           // Paramètres de connexion à la base de données 
            props.setProperty("hibernate.connection.driver_class", JDBC_DRIVER); 
            props.setProperty("hibernate.connection.url", JDBC_URL); 
            props.setProperty("hibernate.connection.username", JDBC_USERNAME); 
            props.setProperty("hibernate.connection.password", JDBC_PASSWORD); 
            props.setProperty("hibernate.dialect", JDBC_DIALECT); 
            hbmcfg.setProperties(props); 
 
            SchemaExport schemaExport = new SchemaExport(hbmcfg); 
            schemaExport.setOutputFile(destination); 
            schemaExport.setFormat(format); 
           // Génération du script sql 
           schemaExport.execute(true, false, justDrop, justCreate); 
            exceptions.addAll(schemaExport.getExceptions()); 
       } catch(ClassNotFoundException exception) { 
            exceptions.add(exception); 
       } 
       if(!exceptions.isEmpty()) { 
            Path filePath = Paths.get(destination); 
           if(Files.notExists(filePath)) { 
                Files.createFile(filePath); 
           } 
            List<String> lines = new ArrayList<>(); 
            lines.add("Certaines exceptions se sont produites lors de la génération du script SQL"); 
            Files.write(filePath, lines, StandardCharsets.UTF_8); 
       } 
   } 
 
   /** 
     * Méthode permettant de récupérer toutes les entités du projet 
     *  
     * @param EntityManagerFactory 
     * @return la listes des entités du projets 
     * @throws ClassNotFoundException 
     * @throws Exception 
     */ 
   protected List<Class<?>> getEntityClass(LocalContainerEntityManagerFactoryBean EntityManagerFactory) throws ClassNotFoundException { 
        List<Class<?>> results = new ArrayList<>(); 
        SessionFactory sessionFactory = EntityManagerFactory 

                                                                .nativeEntityManagerFactory 

                                                                .unwrap(SessionFactory.class); 

 
       for(String clazzName : sessionFactory.getAllClassMetadata().keySet()) { 
            results.add(Class.forName(clazzName)); 
       } 
       return results; 
   } 
} 

JpaSchemaUpdate

JpaSchemaUpdate est une classe utilitaire permettant de comparer les entités du projet avec le schéma de la base de données initiale et de générer le script SQL de remise à niveau de cette dernière.

@RunWith(SpringJUnit4ClassRunner.class) 
@Transactional 
// Chemin du fichier de configuration Spring 
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/beans.xml"}) 
public class JpaSchemaUpdate { 
 
   // Chemin de génération du script sql 
   private static final String DESTINATION_PATH = "src/test/resources/sqldiff.sql"; 
   // Chemin des fichiers d'environnement 
   private static final String ENV_PATH = "src/env/dev"; 
   // Formatter proprement le SQL généré 
   private static final boolean FORMAT = true; 
   // Paramètres de connexion à la base de données initiale, à personnaliser pour chaque projet 
   // utilisant cette classe 
   private static final String JDBC_DIALECT = "org.hibernate.dialect.SQLServer2005Dialect"; 
   private static final String JDBC_DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; 
   private static final String JDBC_URL = "jdbc:sqlserver://127.0.0.1:1433;databaseName=MEDIATHEQUE_DEV;shutdown=false"; 
   private static final String JDBC_USERNAME = "USER"; 
   private static final String JDBC_PASSWORD = "PASSWORD"; 
    

    @Autowired 
    LocalContainerEntityManagerFactoryBean EntityManagerFactory; 
 
   

 static { 
        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); 
        Class<URLClassLoader> clazz = URLClassLoader.class; 
 
       // Use reflection 
       Method method; 
       try { 
            method = clazz.getDeclaredMethod("addURL", new Class[] {URL.class}); 
            method.setAccessible(true); 
            method.invoke(classLoader, new Object[] {new File(ENV_PATH).toURI().toURL()}); 
       } catch(Exception e) { 
            e.printStackTrace(); 
       } 
   } 
 
   @Test 
   public void generer_sql_script_diff() throws Exception { 
        execute(EntityManagerFactory, DESTINATION_PATH, FORMAT); 
   } 
 
   /** 
     * Génère le script sql de synchronisation. 
     *  
     * @param EntityManagerFactory 
     * @param destination chemin de génération du script sql. 
     * @param propertiesPath fichier de paramètres de connexion à la base de données 
     * @param format si true le SQL généré sera formatté proprement 
     * @throws IOException 
     * @throws Exception 
     */ 
   @SuppressWarnings("unchecked") 
   protected void execute(LocalContainerEntityManagerFactoryBean EntityManagerFactory, String destination, boolean format) throws IOException { 
        List<Exception> exceptions = new ArrayList<Exception>(); 
       try { 
            Configuration hbmcfg = new Configuration(); 
           // Ajout de toutes les entités du projet à la configuration 
           for(Class<?> clazz : getEntityClass(EntityManagerFactory)) { 
                hbmcfg.addAnnotatedClass(clazz); 
           } 
            Properties props = new Properties(); 
            props.putAll(hbmcfg.getProperties()); 
           // Paramètres de connexion à la base de données 
            props.setProperty("hibernate.connection.driver_class", JDBC_DRIVER); 
            props.setProperty("hibernate.connection.url", JDBC_URL); 
            props.setProperty("hibernate.connection.username", JDBC_USERNAME); 
            props.setProperty("hibernate.connection.password", JDBC_PASSWORD); 
            props.setProperty("hibernate.dialect", JDBC_DIALECT); 
            hbmcfg.setProperties(props); 
 
            SchemaUpdate schemaUpdate = new SchemaUpdate(hbmcfg); 
            schemaUpdate.setOutputFile(destination); 
            schemaUpdate.setFormat(format); 
           // Génération du script sql 
           schemaUpdate.execute(true, false); 
            exceptions.addAll(schemaUpdate.getExceptions()); 
       } catch(ClassNotFoundException exception) { 
            exceptions.add(exception); 
       } 
       if(!exceptions.isEmpty()) { 
            Path filePath = Paths.get(destination); 
           if(Files.notExists(filePath)) { 
                Files.createFile(filePath); 
           } 
            List<String> lines = new ArrayList<>(); 
            lines.add("Certaines exceptions se sont produites lors de la génération du script SQL"); 
            Files.write(filePath, lines, StandardCharsets.UTF_8); 
       } 
   } 
 
   /** 
     * Méthode permettant de récupérer toutes les entités du projet 
     *  
     * @param EntityManagerFactory 
     * @return la listes des entités du projets 
     * @throws ClassNotFoundException 
     * @throws Exception 
     */ 
   protected List<Class<?>> getEntityClass(LocalContainerEntityManagerFactoryBean EntityManagerFactory) throws ClassNotFoundException { 
        List<Class<?>> results = new ArrayList<>(); 
        SessionFactory sessionFactory = EntityManagerFactory.nativeEntityManagerFactory.unwrap(SessionFactory.class); 
       for(String clazzName : sessionFactory.getAllClassMetadata().keySet()) { 
            results.add(Class.forName(clazzName)); 
       } 
       return results; 
   } 
} 

Génération du script SQL

Afin de générer le script SQL il faut :

  • Copier la classe JpaSchemaUpdate ou JpaSchemaExport dans le dossier "src/test/java" de votre projet ;
  • Configurer les outils de générations, les classes JpaSchemaUpdate et JpaSchemaExport  présentent un ensemble de constantes permettant de les configurer en indiquant les chemins des fichiers d'environnement, du fichier de configuration Spring, du fichier SQL à générer ainsi que les paramètres de connexion à la base de données initiales (voir le code source de la classe pour plus de détails);
  • Lancer la génération du script SQL, pour cela il suffit d’exécuter cette classe en tant que test Junit.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *