当数据包含日文字符时无法使用 org.postgresql.copy.CopyManager 插入数据库

Unable to insert into database using org.postgresql.copy.CopyManager when the data has japanese characters

在过去的几个小时里,我一直在努力弄清楚我的代码有什么问题。这段代码一切正常,直到我收到一个包含日文字符的文件。 Notepad++甚至一些在线实用工具都说文件的编码是UTF-8。记事本显示其 UTF-8-BOM。 我已经从文件中读取了我的数据并对其进行了处理,最后想将其写出到数据库中。

我收到错误 org.postgresql.util.PSQLException:错误:编码字节序列无效 "UTF8":0xee 我的数据库编码只有UTF8..

package citynet.dta.pump;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.postgresql.copy.CopyManager;
import org.postgresql.core.BaseConnection;

import citynet.common.ServerException;

public class TestEncoding {

public static void main(String[] args) {
    byte[] bytes = null;
    try {
        //use the below sql to create table 'testtable'
        // create table testtable (text1 character varying, text2 character varying,text3 character varying)
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            DataOutputStream out = new DataOutputStream(baos);

            out.writeBytes("INR,字仮名交じり文,3255104BTK1");

            bytes = baos.toByteArray();
        }
        Class.forName("org.postgresql.Driver");
        Connection c = DriverManager.getConnection("jdbc:postgresql://server:5432/dbname", "username", "password");
        if (bytes != null) {
            try (ByteArrayInputStream input = new ByteArrayInputStream(bytes)) {
                String sql = "COPY testtable  FROM stdin delimiter ','  NULL AS 'null' ENCODING 'UTF8' ";
                BaseConnection pgcon = (BaseConnection) c;
                CopyManager mgr = new CopyManager(pgcon);
                try {
                    mgr.copyIn(sql, input);

                } catch (SQLException ex) {
                    throw new ServerException("Error while copying data in Postgres DB:" + ex);

                }
            }
        }
    } catch (Exception e) {
        System.out.println("Error:" + e);
    }
  }
}

问题是 DataOutputStream#writeBytes("INR,字仮名交じり文,3255104BTK1") 没有按照您的预期进行。

  1. 您应该避免使用 BaseConnection,因为它是一个内部 class。应用程序代码应使用 PGConnection

  2. 这是你如何获得 CopyManager:

    Connection con = ...;
    PGConnection pgcon = con.unwrap(org.postgresql.PGConnection.class);
    CopyManager mgr = pgcon.getCopyAPI();
    
  3. 您的数据来源可能不同,因此有多种执行方式copyAPI

    如果您想通过自己的代码将 String 转换为 UTF-8 字节,那么您需要 getBytes.

    String sql = "COPY testtable  FROM stdin delimiter ','  NULL AS 'null' ENCODING 'UTF8' ";
    byte[] bytes = "INR,字仮名交じり文,3255104BTK1".getBytes(StandardCharsets.UTF_8);
    mgr.copyIn(sql, new ByteArrayInputStream(bytes));
    

    注意:不需要 close ByteArrayInputStream(参见它的 Javadoc)。

  4. 如果您需要将 CSV 文件流式传输到数据库,您可以使用 FileInputStream:

    try (InputStream fis = new FileInputStream("file.csv")) {
      mgr.copyIn(sql, fis);
    }
    
  5. 如果您想逐步构建内容,则可以使用 ByteArrayOutputStream + OutputStreamWriter

    注意:所有行都需要适合内存,否则你会得到 OutOfMemoryError.

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try (OutputStreamWriter wr = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
      // Write 10 rows
      for (int i = 0; i < 10; i++) {
        wr.write("INR,字仮名交じり文,3255104BTK1\n");
      }
    }
    
    String sql = "COPY testtable  FROM stdin delimiter ','  NULL AS 'null' ENCODING 'UTF8'";
    mgr.copyIn(sql, new ByteArrayInputStream(baos.toByteArray()));
    
  6. 另一种选择是使用 Reader

    注意:未指定编码,它使用 connection-default 编码(在 99.42% 的情况下为 utf-8,因为驱动程序默认为 utf-8连接编码)。

    String sql = "COPY testtable  FROM stdin delimiter ','  NULL AS 'null'";
    mgr.copyIn(sql, new StringReader("INR,字仮名交じり文,3255104BTK1"));
    
  7. 另一种选择是使用 copyIn(String sql, ByteStreamWriter from) API 这对于某些用例可能更有效(例如,所有数据都在内存中,并且您知道您要写入的字节数)