如何在Java中执行SQL脚本文件?

43
我想在Java中执行一个SQL脚本文件,而不需要将整个文件内容读入一个大查询并执行它。是否有其他标准方法?
12个回答

35

如果您不介意在Java中依赖Ant,那么有一种很好的方法可以执行SQL脚本而无需自己阅读它们。在我看来,在您的情况下使用这样的依赖是非常合理的。以下是样例代码,其中SQLExec类位于ant.jar中:

private void executeSql(String sqlFilePath) {
    final class SqlExecuter extends SQLExec {
        public SqlExecuter() {
            Project project = new Project();
            project.init();
            setProject(project);
            setTaskType("sql");
            setTaskName("sql");
        }
    }

    SqlExecuter executer = new SqlExecuter();
    executer.setSrc(new File(sqlFilePath));
    executer.setDriver(args.getDriver());
    executer.setPassword(args.getPwd());
    executer.setUserid(args.getUser());
    executer.setUrl(args.getUrl());
    executer.execute();
}

1
我尝试了一下我的几个Oracle SQL脚本,它适用于insertcreate table。但是对于包含create or replace trigger的脚本,它会失败并显示java.sql.SQLSyntaxErrorException: ORA-00900: invalid SQL statement - Kuldeep Jain
1
也许应该考虑一下你的分隔符。 - carpinchosaurio

24

没有通用的方法可以做到这一点。不过,你可以执行一个本地客户端作为外部程序来完成这个任务:

import java.io.*;
public class CmdExec {

  public static void main(String argv[]) {
    try {
      String line;
      Process p = Runtime.getRuntime().exec
        ("psql -U username -d dbname -h serverhost -f scripfile.sql");
      BufferedReader input =
        new BufferedReader
          (new InputStreamReader(p.getInputStream()));
      while ((line = input.readLine()) != null) {
        System.out.println(line);
      }
      input.close();
    }
    catch (Exception err) {
      err.printStackTrace();
    }
  }
}
  • 代码示例摘自这里,并进行了修改以回答问题,假设用户想要执行一个PostgreSQL脚本文件。

10

Flyway库非常适合这个场景:

    Flyway flyway = new Flyway();
    flyway.setDataSource(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
    flyway.setLocations("classpath:db/scripts");
    flyway.clean();
    flyway.migrate();

此功能扫描脚本位置并按顺序运行它们。脚本可以使用V01__name.sql进行版本控制,因此如果只调用迁移,则仅运行尚未运行的脚本。使用名为'schema_version'的表来跟踪事务。但也可以执行其他操作,请参阅文档:flyway

清理调用不是必需的,但对于从干净的数据库开始很有用。 此外,请注意位置(默认为"classpath:db / migration"),冒号后没有空格,这让我受挫了。


6
不,您必须先读取文件,将其分割成单独的查询,然后逐个执行它们(或使用JDBC的批处理API)。
原因之一是每个数据库都定义了自己的分隔SQL语句的方式(有些使用“;”,其他人使用“/”,有些允许两者都可以甚至定义自己的分隔符)。

3

JDBC不支持此选项,解决这个问题的最好方法是通过Java程序执行命令行。下面是一个postgresql的示例:

private void executeSqlFile() {
     try {
         Runtime rt = Runtime.getRuntime();
         String executeSqlCommand = "psql -U (user) -h (domain) -f (script_name) (dbName)";
         Process pr = rt.exec();
         int exitVal = pr.waitFor();
         System.out.println("Exited with error code " + exitVal);
      } catch (Exception e) {
        System.out.println(e.toString());
      }
}

3

您无法使用JDBC进行操作,因为它不支持。解决方法是包含iBatis iBATIS是一个持久化框架,并按照iBatis文档中所示调用Scriptrunner构造函数。

为了运行简单的SQL脚本,不推荐使用像ibatis这样的重量级持久化框架,您可以使用命令行轻松完成。

$ mysql -u root -p db_name < test.sql

2
对于我的简单项目,用户应该能够选择要执行的SQL文件。 由于我对其他答案不满意,而且我已经在使用Flyway了,所以我仔细研究了Flyway代码。 DefaultSqlScriptExecutor 执行实际操作,因此我尝试找出如何创建 DefaultSqlScriptExecutor 的实例。
基本上,以下代码片段加载一个 String,将其拆分成单个语句并逐个执行。 Flyway还提供了其他 LoadableResource,例如 FileSystemResource,但我没有仔细研究它们。
由于 Flyway 没有正式记录 DefaultSqlScriptExecutor 和其他类,因此请小心使用代码片段。
public static void execSqlQueries(String sqlQueries, Configuration flyWayConf) throws SQLException {
  // create dependencies FlyWay needs to execute the SQL queries
  JdbcConnectionFactory jdbcConnectionFactory = new JdbcConnectionFactory(flyWayConf.getDataSource(),
      flyWayConf.getConnectRetries(),
      null);
  DatabaseType databaseType = jdbcConnectionFactory.getDatabaseType();
  ParsingContext parsingContext = new ParsingContext();
  SqlScriptFactory sqlScriptFactory = databaseType.createSqlScriptFactory(flyWayConf, parsingContext);
  Connection conn = flyWayConf.getDataSource().getConnection();
  JdbcTemplate jdbcTemp = new JdbcTemplate(conn);
  ResourceProvider resProv = flyWayConf.getResourceProvider();
  DefaultSqlScriptExecutor scriptExec = new DefaultSqlScriptExecutor(jdbcTemp, null, false, false, false, null);
  
  // Prepare and execute the actual queries
  StringResource sqlRes = new StringResource(sqlQueries);
  SqlScript sqlScript = sqlScriptFactory.createSqlScript(sqlRes, true, resProv);
  scriptExec.execute(sqlScript);
}

2
Apache iBatis解决方案效果很好。我使用的脚本示例与我从MySQL Workbench运行的脚本完全相同。这里有一篇带有示例的文章:https://www.tutorialspoint.com/how-to-run-sql-script-using-jdbc#:~:text=You%20can%20execute%20.,to%20pass%20a%20connection%20object.&text=Register%20the%20MySQL%20JDBC%20Driver,method%20of%20the%20DriverManager%20class。这是我所做的:pom.xml依赖项。
<!-- IBATIS SQL Script runner from Apache (https://mvnrepository.com/artifact/org.apache.ibatis/ibatis-core) -->
<dependency>
    <groupId>org.apache.ibatis</groupId>
    <artifactId>ibatis-core</artifactId>
    <version>3.0</version>
</dependency>

执行脚本的代码:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.sql.Connection;   
import org.apache.ibatis.jdbc.ScriptRunner;   
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SqlScriptExecutor {

    public static void executeSqlScript(File file, Connection conn) throws Exception {
        Reader reader = new BufferedReader(new FileReader(file));
        log.info("Running script from file: " + file.getCanonicalPath());
        ScriptRunner sr = new ScriptRunner(conn);
        sr.setAutoCommit(true);
        sr.setStopOnError(true);
        sr.runScript(reader);
        log.info("Done.");
    }
    
}

1
iBatis现在被更名为myBatis,但是它的解决方案至今仍然保持不变:<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> - haui

1
我找到的最简单且便携的外部工具是jisql-https://www.xigole.com/software/jisql/jisql.jsp。 您可以这样运行它:
java -classpath lib/jisql.jar:\
          lib/jopt-simple-3.2.jar:\
          lib/javacsv.jar:\
           /home/scott/postgresql/postgresql-8.4-701.jdbc4.jar 
    com.xigole.util.sql.Jisql -user scott -password blah     \
    -driver postgresql                                       \
    -cstring jdbc:postgresql://localhost:5432/scott -c \;    \
    -query "select * from test;"

我使用了 java -classpath lib/jisql-2.0.11.jar:lib/jopt-simple-3.2.jar:lib/javacsv.jar:../ojdbc7.jar com.xigole.util.sql.Jisql -user ecm -password TODO -driver oracle.jdbc.OracleDriver -cstring jdbc:oracle:thin:@localhost:1521:XE -c \; -input myoracle.sql,它完美地运行了。 - Martin Vysny

0

尝试使用这段代码:

String strProc =
         "DECLARE \n" +
         "   sys_date DATE;"+
         "" +
         "BEGIN\n" +
         "" +
         "   SELECT SYSDATE INTO sys_date FROM dual;\n" +
         "" +
         "END;\n";

try{
    DriverManager.registerDriver ( new oracle.jdbc.driver.OracleDriver () );
    Connection connection = DriverManager.getConnection ("jdbc:oracle:thin:@your_db_IP:1521:your_db_SID","user","password");  
    PreparedStatement psProcToexecute = connection.prepareStatement(strProc);
    psProcToexecute.execute();
}catch (Exception e) {
    System.out.println(e.toString());  
}

10
你的例子过于简单了。一个 Statement 通常只能一次执行一个 SQL 语句。问题暗示有大量查询或插入操作,因此需要通过找到分隔符将它们分解成单独的语句。 - BoffinBrain
同意,这个解决方案在多语句脚本上不起作用。请使用下面建议的 jisql。 - Martin Vysny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接