java後端通過資料庫地址等資訊訪問第三方資料庫,以及JdbcTemplate的in語法引數解決辦法
阿新 • • 發佈:2020-10-26
一、通過資料庫資訊訪問第三方資料庫
本例子是從一張儲存資料庫資訊表中查出第三方資料庫資訊,再進行訪問的
1、 對資料庫密碼的加密、解密類
public class SecretHelper { public byte[] random(int len) { byte[] buf = new byte[len]; random.nextBytes(buf); return buf; } public byte[] password(byte[] plain) throws GeneralSecurityException { Mac mac= macKeysetHandle.getPrimitive(Mac.class); return mac.computeMac(plain); } public boolean verify(byte[] cipher, byte[] plain) { try { Mac mac = macKeysetHandle.getPrimitive(Mac.class); mac.verifyMac(cipher, plain); return true; } catch(GeneralSecurityException e) { logger.error("fail on verify password ", e); } return false; } public byte[] encrypt(byte[] plain, byte[] salt) throws GeneralSecurityException { Aead aead = aesKeysetHandle.getPrimitive(Aead.class); return aead.encrypt(plain, salt); }public byte[] decrypt(byte[] cipher, byte[] salt) throws GeneralSecurityException { Aead aead = aesKeysetHandle.getPrimitive(Aead.class); return aead.decrypt(cipher, salt); } @PostConstruct void init() throws IOException, GeneralSecurityException { random = new Random(); final var aes = new File("aes.json"); if (aes.exists()) { logger.info("load aes key from {}", aes); aesKeysetHandle = CleartextKeysetHandle.read(JsonKeysetReader.withFile(aes)); } else { aesKeysetHandle = KeysetHandle.generateNew( AeadKeyTemplates.AES256_GCM); logger.info("generate aes key file {}", aes); CleartextKeysetHandle.write(aesKeysetHandle, JsonKeysetWriter.withFile(aes)); } final var mac = new File("mac.json"); if (mac.exists()) { logger.info("load mac key from {}", mac); macKeysetHandle = CleartextKeysetHandle.read(JsonKeysetReader.withFile(mac)); } else { macKeysetHandle = KeysetHandle.generateNew( MacKeyTemplates.HMAC_SHA256_256BITTAG); logger.info("generate mac key file {}", mac); CleartextKeysetHandle.write(macKeysetHandle, JsonKeysetWriter.withFile(mac)); } } private KeysetHandle aesKeysetHandle; private KeysetHandle macKeysetHandle; private Random random; }
2、第三方資料庫資訊類
public class Fwqsz implements Serializable { @Override public String toString() { return "(" + user + "@" + host + "/" + sid + ")"; } public HikariDataSource open(String password) { return open(user, password); } public HikariDataSource open(String user, String password) { final var url = url(); logger.info("連線 {}@{}", user, url); var ds = new HikariDataSource(); ds.setJdbcUrl(url); ds.setUsername(user); ds.setPassword(password); return ds; } public String url() { return String.format("jdbc:oracle:thin:@%s:1521:%s", host, sid); } public void test(String password) throws SQLException { try ( var conn = DriverManager.getConnection(url(), user, password); var stmt = conn.createStatement(); var rs = stmt.executeQuery("select CURRENT_TIMESTAMP from dual")) { if (rs.next()) { logger.info("test oracle server: timestamp {}", rs.getString(1)); } } } @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(length = 255, nullable = false) private String host; @Column(name = "`user`", length = 20, nullable = false) private String user; @Lob @JsonIgnore @Column(nullable = false) private byte[] salt; @Lob @JsonIgnore @Column(name = "`password`", nullable = false) private byte[] password; @Column(length = 20, nullable = false) private String sid; @Nationalized @Column(length = 100) private String yt; @Type(type = "yes_no") @Column(nullable = false) private boolean enable; @Version private int version; private Date updatedAt; private Date createdAt; //使用時需要實現get、set方法 }
資料庫建表時,salt和password的型別為type="blob"
新增一條第三方資料庫資訊時,密碼需要加密
@Transactional(rollbackFor = Exception.class) @Override public void add(CreateFwqsz form) throws GeneralSecurityException { Fwqsz it = new Fwqsz(); var now = new Date(); it.setHost(form.getHost()); it.setUser(form.getUser()); it.setSalt(secretHelper.random(32)); it.setPassword(secretHelper.encrypt(form.getPassword().getBytes(), it.getSalt())); it.setSid(form.getSid()); it.setYt(form.getYt()); it.setEnable(form.getEnable()); it.setUpdatedAt(now); it.setCreatedAt(now); fwqszRepository.save(it); }
3、訪問第三方資料庫。先對密碼進行解密,new Object[]{}裡面的引數根據前面sql語句?的順序傳入
try (var ds = fwqsz.open(new String(secretHelper.decrypt(fwqsz.getPassword(), fwqsz.getSalt())))) { var open = new JdbcTemplate(ds); List<YecxKm> yecxKmList = = open.query("SELECT SERVERIP,SACC_CODE_C,IYEAR,SCORPCODE,SACC_NAME_C,ILEVEL,NOPEN_BAL FROM SUBJECT_C WHERE SERVERIP=? AND SCORPCODE=? AND IYEAR=? AND (SACC_CODE_C=? OR SACC_CODE_C LIKE ? ) ", new Object[]{corptype, scorpcode, year, saccCodeC, saccCodeC + "-%"}, new QjyecxMapper()); } catch (GeneralSecurityException e) { logger.error("{}", e.getMessage()); throw e; }
注意:
因為連結是 ds 負責管理的,要麼做成單例 給所有的beans用,要麼用完就釋放。把ds放try()內(用完之後自動關閉資料庫連線,上面例子為try語法),否則放finally內close。
4、建一個QjyecxMapper類
public class QjyecxMapper implements RowMapper<YecxKm> { @Override public YecxKm mapRow(ResultSet rs, int rowNum) throws SQLException { var it = new YecxKm(); it.setServerip(rs.getString("SERVERIP")); it.setScorpcode(rs.getString("SCORPCODE")); it.setSaccCodeC(rs.getString("SACC_CODE_C")); it.setSaccNameC(rs.getString("SACC_NAME_C")); it.setIyear(rs.getString("IYEAR")); it.setIlevel(rs.getString("ILEVEL")); it.setNopenBal(rs.getString("NOPEN_BAL")); return it; } }
二、JdbcTemplate的in語法引數解決辦法
上面的JdbcTemplate解決不了in語法,得使用NamedParameterJdbcTemplate
官網文件例子連線:https://www.technicalkeeda.com/spring-tutorials/spring-jdbctemplate-in-clause-example
例子:
var jdbcTemplate = new NamedParameterJdbcTemplate(ds); HashMap<String, Object> params = new HashMap<>(); params.put("corptype", corptype); params.put("scorpcode", scorpcode); params.put("iyear", iyear); params.put("sdocids", list); List<Pzmxzb> pzmxzbList=open.query("SELECT SDOC_ID,SDOC_NO,SDOC_TYPE,SENTRIEDBY,SPERIODCODE,DDOC_DATE FROM GL_DOC_M WHERE SERVERIP=:corptype AND SCORPCODE=:scorpcode AND IYEAR=:iyear AND SDOC_ID IN (:sdocids)", params, new PzmxzbMapper());
list為集合,比如List<String>字串集合
注意:oracle的in語法的集合長度不能超過1000,否則會報錯,其實超過200就不合理了