使用 JPA 备份数据库(设计)

jav*_*csw 5 jpa database-backups

我有一个工作代码,但并不总是有效。这是我的方法:

\n\n

创建备份

\n\n
    \n
  1. 为源数据库创建实体管理器
  2. \n
  3. 为目标数据库创建实体管理器(嵌入式 Derby 数据库)
  4. \n
  5. 复制实体(选择表的所有条目(现在对表顺序进行硬编码)并将它们复制到目标数据库。基本上是全选并从源分离实体并保留在目标上)
  6. \n
  7. 压缩嵌入式 Derby 数据库。
  8. \n
\n\n

从备份加载

\n\n
    \n
  1. 解压备份
  2. \n
  3. 执行备份
  4. \n
  5. 清理目标数据库(删除所有表)
  6. \n
  7. 复制实体
  8. \n
\n\n

在某些时候,我会使用 JPA 2 元数据来获取要复制的表并选择它们需要复制的顺序(由于约束)。

\n\n

由于某种原因,这种方法并不总是有效,因为我看到“丢失”的条目未恢复。

\n\n

这是代码:

\n\n
package com.bluecubs.xinco.core.server;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.text.SimpleDateFormat;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.ArrayList;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\nimport java.util.zip.ZipOutputStream;\nimport javax.persistence.EntityManager;\nimport javax.persistence.EntityManagerFactory;\nimport javax.persistence.Persistence;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.filefilter.IOFileFilter;\nimport org.apache.commons.io.filefilter.TrueFileFilter;\n\n/**\n * This is a complex task and is heavily dependant on the architecture\n * of the database.\n * \n * Data needs to be stored in a particular order into the database to comply \n * with database constraints. This order can be observed in a dump file or\n * create script like the ones generated from MySQL Workbench. Using that \n * should be enough. In case that tool is not available basically the logic is\n * populating tables from the outside inwards. From the tables with no relationships\n * or only one working to the more complex ones. As summary before a table is populated all\n * the related tables should be populated already (if we have identifying relationships.\n *\n * @author Javier A. Ortiz Bultr\xc3\xb3n <javier.ortiz.78@gmail.com>\n */\npublic class XincoBackupManager {\n\n    private static XincoBackupManager instance;\n    private static EntityManagerFactory liveEMF;\n    private static EntityManagerFactory backupEMF;\n    private static EntityManager live, backup;\n    private static final ArrayList<String> tables = new ArrayList<String>();\n    private static XincoBackupFile last;\n    private static String backupPath;\n    public static HashMap<String, Integer> stats = new HashMap<String, Integer>();\n\n    static {\n        //Non-order-critical tables\n        tables.add("XincoCoreAceT");\n        tables.add("XincoCoreDataT");\n        tables.add("XincoCoreDataTypeAttributeT");\n        tables.add("XincoCoreGroupT");\n        tables.add("XincoCoreLanguageT");\n        tables.add("XincoCoreNodeT");\n        tables.add("XincoCoreUserHasXincoCoreGroupT");\n        tables.add("XincoCoreUserT");\n        tables.add("XincoSettingT");\n        tables.add("XincoDependencyTypeT");\n        tables.add("XincoCoreDataHasDependencyT");\n        tables.add("XincoSetting");\n        tables.add("XincoId");\n        //Order critical tables\n        tables.add("XincoCoreLanguage");\n        tables.add("XincoCoreNode");\n        tables.add("XincoCoreDataType");\n        tables.add("XincoCoreData");\n        tables.add("XincoDependencyType");\n        tables.add("XincoCoreDataHasDependency");\n        tables.add("XincoCoreUser");\n        tables.add("XincoCoreUserModifiedRecord");\n        tables.add("XincoCoreGroup");\n        tables.add("XincoCoreAce");\n        tables.add("XincoCoreUserHasXincoCoreGroup");\n        tables.add("XincoAddAttribute");\n        tables.add("XincoCoreDataTypeAttribute");\n        tables.add("XincoCoreLog");\n    }\n\n    public static XincoBackupManager get() {\n        if (instance == null) {\n            instance = new XincoBackupManager();\n        }\n        return instance;\n    }\n\n    private static void setDBSystemDir(String systemDir) {\n        // Set the db system directory.\n        System.setProperty("derby.system.home", systemDir);\n        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                "Derby home set at: {0}", systemDir);\n        try {\n            //Start the embeded DB\n            Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();\n        } catch (ClassNotFoundException ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n        } catch (InstantiationException ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n        } catch (IllegalAccessException ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n        }\n    }\n\n    private static void initConnections() {\n        try {\n            liveEMF = XincoDBManager.getEntityManagerFactory();\n        } catch (XincoException ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n        }\n        try {\n            backupEMF = Persistence.createEntityManagerFactory("XincoBackup");\n        } catch (Exception ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n        }\n    }\n\n    protected static boolean backup() throws XincoException {\n        try {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Initializing connections...");\n            initConnections();\n            stats.clear();\n            backupPath = XincoSettingServer.getSetting("setting.backup.path").getString_value();\n            //We need to make sure that there\'s no one in the database\n            XincoDBManager.setLocked(true);\n            live = liveEMF.createEntityManager();\n            //Prepare the backup repository. Create dirs if needed.\n            File backupDir = new File(backupPath);\n            backupDir.mkdirs();\n            //Create folder for this backup\n            SimpleDateFormat format = new SimpleDateFormat("MM-dd-yyyy");\n            File backupNewDir = new File(backupPath + System.getProperty("file.separator")\n                    + format.format(new Date()));\n            backupNewDir.mkdirs();\n            /*\n             * Make sure there\'s no derby database stuff in the folder.\n             * Any previous interrupted backup might left corrupted database files.\n             */\n            File tempDir = new File(backupNewDir.getAbsolutePath()\n                    + System.getProperty("file.separator") + "xinco");\n            if (tempDir.exists()) {\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.WARNING,\n                        "Deleting potentially corrupted database files at: {0}", tempDir);\n                FileUtils.deleteDirectory(tempDir);\n                //Delete Derby log file\n                FileUtils.forceDelete(new File(backupNewDir.getAbsolutePath()\n                        + System.getProperty("file.separator") + "derby.log"));\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO,\n                        "Done!");\n            }\n            /**\n             * Prepare system to use derby\n             */\n            setDBSystemDir(backupNewDir.getAbsolutePath());\n            backup = backupEMF.createEntityManager();\n            for (String s : tables) {\n                copyEntities(s, live, backup);\n            }\n            /**\n             * At this point we should have a <Backup Database name> folder in\n             * <Backup Path>/<Date>.\n             * Lets zip them for storage.\n             */\n            format = new SimpleDateFormat("MM dd yyyy hh-mm-ss");\n            zipBackupFiles(backupNewDir, backupNewDir.getAbsolutePath()\n                    + System.getProperty("file.separator") + "Xinco Backup " + format.format(new Date()));\n            //Stop Derby database in order to delete\n            try {\n                DriverManager.getConnection("jdbc:derby:;shutdown=true");\n            } catch (SQLException e) {\n                //When the database shuts down it\'ll throw an exception\n            }\n            //Delete backed up files\n            String dbName = (String) backup.getProperties().get("javax.persistence.jdbc.url");\n            dbName = dbName.substring(dbName.lastIndexOf(":") + 1, dbName.indexOf(";"));\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Deleting temp folder: {0}", dbName);\n            FileUtils.deleteDirectory(new File(backupNewDir.getAbsolutePath()\n                    + System.getProperty("file.separator") + dbName));\n            //Delete Derby log file\n            FileUtils.forceDelete(new File(backupNewDir.getAbsolutePath()\n                    + System.getProperty("file.separator") + "derby.log"));\n        } catch (XincoException ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n            XincoDBManager.setLocked(false);\n            return false;\n        } catch (Exception ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n            XincoDBManager.setLocked(false);\n            return false;\n        } finally {\n            if (live != null && live.isOpen()) {\n                live.close();\n            }\n            if (backup != null && backup.isOpen()) {\n                backup.close();\n            }\n            if (backupEMF != null && backupEMF.isOpen()) {\n                backupEMF.close();\n            }\n        }\n        XincoDBManager.setLocked(false);\n        return true;\n    }\n\n    private static void zipBackupFiles(File path, String zipName) throws XincoException {\n        if (!zipName.endsWith(".zip")) {\n            zipName += ".zip";\n        }\n        // These are the files to include in the ZIP file\n        IOFileFilter filter = new IOFileFilter() {\n\n            @Override\n            public boolean accept(File file) {\n\n                if (file.isDirectory()) {\n                    return true;\n                }\n                //Ignore other backup files\n                if (file.isFile() && !file.getName().endsWith(".zip")) {\n                    return true;\n                }\n                return false;\n            }\n\n            @Override\n            public boolean accept(File file, String string) {\n                throw new UnsupportedOperationException("Not supported yet.");\n            }\n        };\n        @SuppressWarnings("unchecked")\n        Collection<File> fileList = FileUtils.listFiles(path, filter, TrueFileFilter.INSTANCE);\n        Object[] files = fileList.toArray();\n\n        // Create a buffer for reading the files\n        byte[] buf = new byte[1024];\n\n        try {\n            // Create the ZIP file\n            ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipName));\n\n            // Compress the files\n            for (int i = 0; i < files.length; i++) {\n                FileInputStream in = new FileInputStream((File) files[i]);\n                String fileName = ((File) files[i]).getPath();\n                //Remove not needed folders\n                fileName = fileName.substring(fileName.indexOf(path.getAbsolutePath()) + path.getAbsolutePath().length() + 1);\n                // Add ZIP entry to output stream.\n                out.putNextEntry(new ZipEntry(fileName));\n\n                // Transfer bytes from the file to the ZIP file\n                int len;\n                while ((len = in.read(buf)) > 0) {\n                    out.write(buf, 0, len);\n                }\n\n                // Complete the entry\n                out.closeEntry();\n                in.close();\n                last = new XincoBackupFile(new File(zipName));\n            }\n            // Complete the ZIP file\n            out.close();\n        } catch (IOException e) {\n            throw new XincoException("Error zipping backup: " + e.getLocalizedMessage());\n        }\n    }\n\n    private static void copyEntities(String table, EntityManager source, EntityManager dest) {\n        List<Object> result, result2;\n        result = source.createNamedQuery(table + ".findAll").getResultList();\n        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO,\n                "Copying from table: {0}", table);\n        int i = 0;\n        source.clear();\n        for (Object o : result) {\n            i++;\n            Class<?> persistenceClass = null;\n            try {\n                persistenceClass = Class.forName("com.bluecubs.xinco.core.server.persistence." + table);\n                dest.getTransaction().begin();\n                if (dest.contains(persistenceClass.cast(o))) {\n                    //If no exception do a merge because it exists already\n                    dest.merge(persistenceClass.cast(o));\n                } else {\n                    dest.persist(persistenceClass.cast(o));\n                }\n                dest.getTransaction().commit();\n            } catch (ClassNotFoundException ex) {\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n                throw new XincoException("No persistence enitiy defined for table: " + table);\n            }catch (Exception ex) {\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n                throw new XincoException("Exception copying: " + o);\n            }\n        }\n        stats.put(table, i);\n        result2 = dest.createNamedQuery(table + ".findAll").getResultList();\n        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO,\n                "Copying for table: {0} completed! Amount of records: {1}",\n                new Object[]{table, i});\n        //Make sure the copy is accurate.\n        //TODO: For some reason XincoId always return twice the amount of records during this routine.\n        if (result2.size() != result.size() && !table.equals("XincoId")) {\n            throw new XincoException("Error copying records for table " + table + ". Got " + result2.size() + " instead of " + result.size());\n        }\n        result2.clear();\n    }\n\n    @SuppressWarnings({"unchecked"})\n    public static ArrayList<XincoBackupFile> getBackupFiles() throws XincoException {\n        // These are the files to include in the ZIP file\n        IOFileFilter filter = new IOFileFilter() {\n\n            @Override\n            public boolean accept(File file) {\n                //Only zip files\n                if (file.isFile() && file.getName().endsWith(".zip")\n                        && file.getName().startsWith("Xinco Backup")) {\n                    return true;\n                }\n                return false;\n            }\n\n            @Override\n            public boolean accept(File file, String string) {\n                throw new UnsupportedOperationException("Not supported yet.");\n            }\n        };\n        Collection<File> files = FileUtils.listFiles(\n                new File(backupPath), filter, TrueFileFilter.INSTANCE);\n        ArrayList<XincoBackupFile> backupFiles = new ArrayList<XincoBackupFile>();\n        for (File f : files) {\n            backupFiles.add(new XincoBackupFile(f));\n        }\n        //Sort\n        Collections.sort(backupFiles, new XincoBackupComparator());\n        //Sorted from oldest to newer so we need to invert the list.\n        Collections.reverse(backupFiles);\n        return backupFiles;\n    }\n\n    protected static boolean restoreFromBackup(XincoBackupFile backupFile) throws XincoException {\n        try {\n            stats.clear();\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Restoring database from: {0}", backupFile.getName());\n            //First make a backup of current database just in case\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Creating a restore point for your current database...");\n            backup();\n            //We need to make sure that there\'s no one in the database\n            XincoDBManager.setLocked(true);\n            //Load database from the provided backup\n            loadDatabaseFromBackup(backupFile);\n            XincoDBManager.setLocked(false);\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Restore complete!");\n            try {\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                        "Deleting restore point...");\n                FileUtils.forceDelete(last);\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                        "Done!");\n            } catch (IOException ex) {\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n            }\n            return true;\n        } catch (XincoException ex) {\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE, null, ex);\n            //Recover from last backup\n            loadDatabaseFromBackup(getLast());\n            XincoDBManager.setLocked(false);\n            throw new XincoException("Unable to load backup! Database reverted to original state. \\n" + ex.getMessage());\n        }\n    }\n\n    protected static void loadDatabaseFromBackup(XincoBackupFile backupFile) throws XincoException {\n        EntityManager backupEM = null;\n        try {\n            initConnections();\n            live = liveEMF.createEntityManager();\n            //Unzip backup\n            unzipBackup(backupFile);\n            //Delete current database (inverse order than writing)\n            Collections.reverse(tables);\n            for (String s : tables) {\n                clearTable(s, live);\n            }\n            //Get back to original order\n            Collections.reverse(tables);\n            //Make derby start where the backup is\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Connecting to backup data...");\n            setDBSystemDir(backupPath + "Temp"\n                    + System.getProperty("file.separator"));\n            //Connect to backup database\n            backupEM = Persistence.createEntityManagerFactory("XincoBackup").createEntityManager();\n            //Start copying\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Starting loading entities...");\n            for (String s : tables) {\n                //Copy values from backup\n                copyEntities(s, backupEM, live);\n            }\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Load complete!");\n            //Stop Derby database in order to delete\n            DriverManager.getConnection("jdbc:derby:;shutdown=true");\n            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINEST,\n                    "Delete temp folder!");\n            try {\n                FileUtils.deleteDirectory(new File(System.getProperty("derby.system.home")));\n            } catch (IOException ex) {\n                Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(

Pas*_*ent 2

设计上有什么缺陷吗?有更好的方法吗?任何评论都非常受欢迎!

大多数数据库引擎提供命令或工具,允许转储给定数据库的内容(其中一些甚至支持增量备份)。当您准备好使用解决方案时,JPA 只会效率较低、更加复杂,因此我不认为使用 JPA 来完成此任务有什么意义。

对于 Derby,实际上无需执行任何操作:只需 zip/tar(或使用 rsync)数据库文件即可完成。

如果要将一个数据库引擎的内容复制到另一个引擎,请使用 ETL。

也可以看看