从以前的数据库中导入数据是许多项目必不可少的步骤,如果数据库大的话,还是挺费时间的。如果能利用GPars将批量数据导入过程并行的话,性能还是能提高不少的。
Tomas Lin给我们分享了他使用Grails和GSQL开发批量数据导入脚本,并用GPars并行化此过程的方法。
1. 用Grails和GSQL开发的脚本
使用GPars之前的代码样例如下,从中我们也可复习一下GSQL及Grails API的相关用法:
import groovy.sql.Sql
import org.codehaus.groovy.grails.commons.ConfigurationHolder
/* Imports photos into our Grails application */
def photoImport = {
def sql = Sql.newInstance(ConfigurationHolder.config.db as String,
ConfigurationHolder.config.dbUsername as String,
ConfigurationHolder.config.dbPassword as String,
"com.mysql.jdbc.Driver")
/* go through each row of photos */
sql.eachRow("""SELECT * from photos""") { photo ->
// get the user
def user = User.findByUsername( photo.username )
if (!user) {
println 'could not locate user: ' + photo.userid
return;
} else {
// creates a new album if the user does not have one
def album = Album.findByUser( user )
if( !album ){
album = new Album( title : "${user.username}'s album", user: user )
}
// import data from database into the photo list
album.addToPhotos(caption: photo.title, description: photo.description, album: album)
// save modified album
if (!album.save(flush: true)){
println "could not save album : " + album.errors
}
}
}
}.call()
几点说明:
- 该脚本是从photo表中将数据导入到新数据库中,这里只是说明主体思路。
- 用{...}.call()是为了能在grails shell或grails console中都能够调用该脚本。(如果用Gant脚本,还得做许多设置工作,而Grails shell或Grails console可以自动处理这些设置)
- 由于产品数据源与开发数据源放在不同地方,这些不同环境信息保存在Config.groovy中
2. 加入GPars
为了利用GPars,配置及代码需要做少量改动:
- 包含GPars的相关.jar文件,或者在grails-app/conf/BuildConfig.groovy文件中加入:
dependencies { build 'org.codehaus.gpars:gpars:0.10' build 'org.coconut.forkjoin.jsr166y:jsr166y:070108' } - 在代码中加入GPars,但由于JDBC的ResultSet不支持并发遍历机制,因此要改用sql.rows(),这里有说明。
sql.eachRow("""SELECT * from photos""") { photo -> ... }改为
GParsPool.withPool { sql.rows( """select * from photos""" ).eachParallel { photo -> ... } } - 由于并发产生的新线程没有正确的hibernate session,因此,如果直接运行上面所改代码,会报"HibernateException: No Hibernate Session bound to thread..."错误。可以用withTransaction闭包包一下GORM代码,为运行GORM代码,它将获取已有session或创建一个新的。
user = User.findByUsername( photo.username )
改为
User.withTransaction{ user = User.findByUsername( photo.username ) } - 为避免StaleObjectExceptions和OptimisticLockExceptions,可以使用悲观数据库锁。
def album = Album.findByUser( user ) if( !album ){ album = new Album( title : "${user.username}'s album", user: user ) }改为
def album = Album.findByUser( user ) if( !album ){ album = new Album( title : "${user.username}'s album", user: user ) } else { // add a pessimistic lock to the album album = Album.lock( album.id ) }
修改后完整的代码样例请参考原文: Writing batch import scripts with Grails, GSQL and GPars
据Tomas Lin“交待”,改造之后,速度大大提高,所花费时间为原来的1/3(原文有对比表格为证)。就改这么几行代码,成果还是比较卓著的。他还提到Spring Batch开发起来比较麻烦而且性能无法保证。
相关资源:

最新评论
9 周 6 小时之前
9 周 1 天之前
11 周 5 天之前
11 周 5 天之前
17 周 2 天之前
17 周 4 天之前
19 周 3 天之前
20 周 1 天之前
20 周 2 天之前
20 周 5 天之前