RE: Upgrade step for localizing existing service builder table

Mirko Romstadt, modified 5 Years ago. New Member Posts: 17 Join Date: 7/23/14 Recent Posts
Hello everybody,

This is a follow up of https://liferay.dev/forums/-/message_boards/message/119475477. I am localizing a portlet that uses the service builder to create a Liferay service and the database tables.

I need to write an upgrade step for Liferay 7.1 to populate the new localization table with the data from the entity table that will eventually drop the now localized columns. As a simplified example:


Before the upgrade step:

Table MyEntity, columns: myEntityId, groupId, title, description

After the upgrade step:

Table MyEntity, columns: myEntityId, groupId, defaultLanguageId
Table MyEntityLocalization, columns: myEntityLocalizationId, myEntityId, languageId, title, description


My idea now is to break down the upgrade step into these steps:

1. Add column defaultLanguageId to MyEntity
2. Get the site's default language for each entry and write to table MyEntity
3. Create Table MyEntityLocalization
4. Populate Table MyEntityLocalization
4.1 Iterate each row in MyEntity
4.2 Create a MyEntityLocalization entry, set entityId, languageId (same as the MyEntry's defaultLanguageId), title and description
5. Drop columns title and description from MyEntity

Steps 1, 3 and 5 are simple. But for step 2 and especially 4 I need some help.

As far as I know, I cannot use the MyEntityLocalServiceUtil in the upgrade step, because the service is stopped. This makes sense to me, but makes it much harder to alter the database.

For step 2 I could imagine to first get all sites, then iterate them and execute an sql query to update the MyEntity table "WHERE groupId=?". There shouldn't be any entries in MyEntity that do have a groupId of a not existing site, so all entries should be updated.

For step 4 I do not have a good idea. How do I get the list of all MyEntities that I need to create an entry for in MyEntityLocalization? I also thought of doing an "INSERT INTO MyEntityLocalization (columns...) SELECT columns... FROM MyEntity", but to insert into the MyEntityLocalization table, I need a unique myEntityLocalizationId. I could manually count up the id's, but for that I would need to create a procedure with a loop and then insert each entry individually. This also contradicts the idea of using Liferay functionality to generate the id's. I would like to use CounterLocalServiceUtil.increment() here, but I don't know how. Any ideas?

Am I missing some Liferay functionality that may help me to update my service tables? Very helpful would be a method to get a List<MyEntity>.

Thanks,
Mirko
Mirko Romstadt, modified 5 Years ago. New Member Posts: 17 Join Date: 7/23/14 Recent Posts
Hey David,

Thanks for your help! After coming back from my vacation I wondered why I didn't take a look into the upgrade steps of the journal service from the very beginning! But it is as André said, sometimes you do not see the wood for the trees ...

Anyway, I adapted Liferay's upgrade implementation to our needs and simplified it a bit as we did not use localization before, so no need for us to transform any xml. For completeness here is the code that works fine for us:


public class UpgradeMyEntityLocalization extends UpgradeProcess {
    
    @Override
    protected void doUpgrade() throws Exception {
        
        addTableMyEntityLocalization();
        
        updateMyEntityDefaultLanguageId();
        
        updateMyEntityLocalizationFields();
        
        alter(MyEntityImpl.class, new AlterTableDropColumn("title"));
        alter(MyEntityImpl.class, new AlterTableDropColumn("description"));
    }
    
    private void addTableMyEntityLocalization() throws Exception {
        
        if (!hasTable(MyEntityLocalizationModelImpl.TABLE_NAME)) {
            
            runSQL(new DBTypeToSQLMap(MyEntityLocalizationModelImpl.TABLE_SQL_CREATE));
            
            runSQL("create unique index IX_BDAEBA79 on MyEntityNamespace_MyEntityLocalization (myEntityId, languageId[$COLUMN_LENGTH:75$]);\n");
        }
    }
    
    private void updateMyEntityDefaultLanguageId() throws Exception {
        
        if (!hasColumn(MyEntityModelImpl.TABLE_NAME, "defaultLanguageId")) {
            
            alter(MyEntityImpl.class, new AlterTableAddColumn("defaultLanguageId VARCHAR(75) null"));
        }
        
        try (PreparedStatement selectFromMyEntityPreparedStatement = connection.prepareStatement(StringBundler
                .concat("select myEntityId, groupId from ", MyEntityModelImpl.TABLE_NAME,
                        " where defaultLanguageId is null or defaultLanguageId = ''"));
                PreparedStatement updateMyEntityPreparedStatement = AutoBatchPreparedStatementUtil
                        .concurrentAutoBatch(connection,
                                "update " + MyEntityModelImpl.TABLE_NAME + " set defaultLanguageId = ? where "
                                        + "myEntityId = ?");
                ResultSet resultSet = selectFromMyEntityPreparedStatement.executeQuery()) {
            
            while (resultSet.next()) {
                
                updateMyEntityPreparedStatement
                        .setString(1, LocaleUtil.toLanguageId(PortalUtil.getSiteDefaultLocale(resultSet.getLong(2))));
                updateMyEntityPreparedStatement
                        .setLong(2, resultSet.getLong(1));
                
                updateMyEntityPreparedStatement.addBatch();
            }
            
            updateMyEntityPreparedStatement.executeBatch();
        }
    }
    
    private void updateMyEntityLocalizationFields() throws Exception {
        
        StringBundler insertIntoMyEntityLocalizationSQL = new StringBundler(3);
        
        insertIntoMyEntityLocalizationSQL.append("insert into ");
        insertIntoMyEntityLocalizationSQL.append(MyEntityLocalizationModelImpl.TABLE_NAME);
        insertIntoMyEntityLocalizationSQL
                .append(" (myEntityLocalizationId, companyId, myEntityId, ");
        insertIntoMyEntityLocalizationSQL.append("languageId, title, description) ");
        insertIntoMyEntityLocalizationSQL.append("values(?, ?, ?, ?, ?, ?)");
        
        try (PreparedStatement selectFromMyEntityPreparedStatement = connection.prepareStatement(
                "select myEntityId, companyId, title, description, defaultLanguageId from "
                        + MyEntityModelImpl.TABLE_NAME);
                PreparedStatement insertIntoMyEntityLocalizationPreparedStatement = AutoBatchPreparedStatementUtil
                        .concurrentAutoBatch(connection, insertIntoMyEntityLocalizationSQL.toString());
                ResultSet selectFromMyEntityResultSet = selectFromMyEntityPreparedStatement
                        .executeQuery()) {
            
            while (selectFromMyEntityResultSet.next()) {
                
                insertIntoMyEntityLocalizationPreparedStatement
                        .setLong(1, CounterLocalServiceUtil.increment());
                insertIntoMyEntityLocalizationPreparedStatement
                        .setLong(2, selectFromMyEntityResultSet.getLong(2));
                insertIntoMyEntityLocalizationPreparedStatement
                        .setLong(3, selectFromMyEntityResultSet.getLong(1));
                insertIntoMyEntityLocalizationPreparedStatement
                        .setString(4, selectFromMyEntityResultSet.getString(5));
                insertIntoMyEntityLocalizationPreparedStatement
                        .setString(5, selectFromMyEntityResultSet.getString(3));
                insertIntoMyEntityLocalizationPreparedStatement
                        .setString(6, selectFromMyEntityResultSet.getString(4));
                
                insertIntoMyEntityLocalizationPreparedStatement.addBatch();
            }
            
            insertIntoMyEntityLocalizationPreparedStatement.executeBatch();
        }
    }
    
}


Greetings,
Mirko
thumbnail
André Bräkling, modified 5 Years ago. Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
Hi David,thanks a lot for the hint! Mirko is on vacation this week, but we will have a look on how it's solved there the next days and tell you if we were successful this way.Best regards
André
thumbnail
David H Nebinger, modified 5 Years ago. Liferay Legend Posts: 14933 Join Date: 9/2/06 Recent Posts
The steps Mirko listed are close to those implemented in Liferay's solution, but Liferay's advantage is that this upgrade process has probably run millions of times now emoticon

There's probably a lot of code you could "borrow" for your own implementation so you wouldn't have to generate it all from scratch...
thumbnail
André Bräkling, modified 5 Years ago. Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
David H Nebinger:

There's probably a lot of code you could "borrow" for your own implementation so you wouldn't have to generate it all from scratch...
Yes, you are absolutely right! But sometimes, you are not able to see the forest for the trees. ;-)We were looking into the JournalArticle code to find a solution for our other ticket (sadly, we just found an implementation marked as "deprecated without replacement" there), but didn't check for the upgrade step there.