views:

115

answers:

1

I'm trying to use the JPA2 metadata to figure out the order to insert/delete rows from a database so constraints are not an issue (to be used later in Java code). This is part of a backup/restore approach using JPA.

Here's my approach:

  1. Group tables by amount of relationships/constraints (Only one-to-many and one-to-one are considered)
  2. Tables with zero instances (as per #1) can have records added/deleted without issue
  3. Tables with one instance can have records added/deleted without issue as long as the related table is already "ready"

Ready by ready I mean that all its related tables records are populated so foreign keys are valid for insert or that there are no other tables referencing to records in this table.

I'm sure it'll be some kind of recursive approach but I got stuck. Any help is more than welcomed.

Here's the code so far:

/**
 * Get the execution order from the EntityManager meta data model.
 *
 * This will fail if the EntityManager is not JP2 compliant
 * @param em EntityManager to get the metadata from
 * @return ArrayList containing the order to process tables
 */
protected static ArrayList<String> getProcessingOrder(EntityManager em) {
    ArrayList<String> tables = new ArrayList<String>();
    //This holds the amount of relationships and the tables with that same amount
    HashMap<Integer, ArrayList<String>> tableStats = new HashMap<Integer, ArrayList<String>>();
    //This holds the table and the tables referenced by it
    HashMap<String, ArrayList<String>> references = new HashMap<String, ArrayList<String>>();
    for (EntityType et : em.getMetamodel().getEntities()) {
        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINER, et.getName());
        int amount = 0;
        Iterator<SingularAttribute> sIterator = et.getSingularAttributes().iterator();
        while (sIterator.hasNext()) {
            SingularAttribute next = sIterator.next();
            switch (next.getPersistentAttributeType()) {
                case BASIC:
                case ELEMENT_COLLECTION:
                case EMBEDDED:
                case ONE_TO_MANY:
                case ONE_TO_ONE:
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINER,
                            "Ignoring: {0}", next.getName());
                    break;
                case MANY_TO_MANY:
                case MANY_TO_ONE:
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO,
                            "{3} has a {2} relationship: {0} with: {1}",
                            new Object[]{next.getName(), next.getBindableJavaType(),
                                next.getPersistentAttributeType().name(), et.getName()});
                    if (!references.containsKey(et.getName())) {
                        references.put(et.getName(), new ArrayList<String>());
                    }
                    references.get(et.getName()).add(next.getBindableJavaType().getSimpleName());
                    amount++;
                    break;
                default:
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE,
                            "Unexpected value: {0}", next.getName());
                    break;
            }
        }
        Iterator<PluralAttribute> pIterator = et.getPluralAttributes().iterator();
        while (pIterator.hasNext()) {
            PluralAttribute next = pIterator.next();
            switch (next.getPersistentAttributeType()) {
                case BASIC:
                case ELEMENT_COLLECTION:
                case EMBEDDED:
                case ONE_TO_MANY:
                case MANY_TO_MANY:
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINER,
                            "Ignoring: {0}", next.getName());
                    break;
                case MANY_TO_ONE:
                case ONE_TO_ONE:
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO,
                            "{3} has a {2} relationship: {0} with: {1}",
                            new Object[]{next.getName(), next.getBindableJavaType(),
                                next.getPersistentAttributeType().name(), et.getName()});
                    if (!references.containsKey(et.getName())) {
                        references.put(et.getName(), new ArrayList<String>());
                    }
                    references.get(et.getName()).add(next.getBindableJavaType().getSimpleName());
                    amount++;
                    break;
                default:
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.SEVERE,
                            "Unexpected value: {0}", next.getName());
                    break;
            }
        }
        if (!tableStats.containsKey(amount)) {
            tableStats.put(amount, new ArrayList<String>());
        }
        tableStats.get(amount).add(et.getName());
    }
    Iterator<String> iterator = references.keySet().iterator();
    while (iterator.hasNext()) {
        String next = iterator.next();
        Iterator<String> iterator1 = references.get(next).iterator();
        StringBuilder refs = new StringBuilder();
        while (iterator1.hasNext()) {
            refs.append(iterator1.next()).append("\n");
        }
        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINER, "References for {0}:\n{1}", new Object[]{next, refs.toString()});
    }
    //Need to sort entities with relationships even further
    ArrayList<String> temp = new ArrayList<String>();
    for (Entry<Integer, ArrayList<String>> e : tableStats.entrySet()) {
        if (e.getKey() > 0) {
            Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO, "Tables with {0} references", e.getKey());
            for (String t : e.getValue()) {
                //Check the relationships of the tables
                //Here's where I need help
                boolean ready = true;
                for (String ref : references.get(t)) {
                    if (!temp.contains(ref)) {
                        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO,
                                "{0} is not ready. Referenced table {1} is not ready yet", new Object[]{t, ref});
                        ready = false;
                    }
                }
                if (ready) {
                    Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.INFO, "{0} is ready.", t);
                    temp.add(t);
                }
            }
            //-------------------------------------------------------
        } else {
            temp.addAll(e.getValue());
        }
    }
    for (Entry<Integer, ArrayList<String>> e : tableStats.entrySet()) {
        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINER,
                "Amount of relationships: {0}", e.getKey());
        StringBuilder list = new StringBuilder();
        for (String t : e.getValue()) {
            list.append(t).append("\n");
        }
        Logger.getLogger(XincoBackupManager.class.getSimpleName()).log(Level.FINER, list.toString());
    }
    tables.addAll(temp);
    return tables;
}
A: 

I'd approach this problem with database metadata from JDBC.

The following methods from java.sql.DatabaseMetadata are to be used here :

// to get the tables
getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) 

// to get the reference to the table
public ResultSet getExportedKeys(String catalog,
                                 String schema,
                                 String table)
                          throws SQLException

I have used this approach in a few applications and it works quite fine.

Although this approach doesn't follow the JPA metamodel usage, I believe operating on the JDBC metadata level is more appropriate given your problem.

As there can be cyclic dependencies which are difficult to handle via such a foreign key dependency graph you could alternatively

for delete

  • disable constraints
  • delete content
  • enable constraints

for add

  • disable constraints
  • add content
  • enable constraints
Timo Westkämper
I have no issues getting tables and its references from JPA2, the question is how to programatically determine the order for deletion/addition of records based on that information.
javydreamercsw
It should be the same issue even if I use your approach.
javydreamercsw
Ok, I updated my answer. Given the possibility of cyclic dependencies, the constraint disabling should be the simplest approach.
Timo Westkämper
Your approach should work but I want to stay away from direct jdbc access since I can't predict the target database engine. I need something more generic like JPA2. Based on the API it should be doable, just trying to figure it out. Sadly in JPA there's nothing like disable/enable constraints.
javydreamercsw