纯代码实现Oracle不同数据库之间的数据迁移
不同Oracle数据库之间数据迁移的实现,只要主表的数据发生了变化,那么就要自动同步到从库中
mvn依赖
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
数据库连接
/**
* 数据库连接类
* @author pihao
*
*/
public class DBTools {
static {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 创建一个数据库链接
*
* @return
* @throws ClassNotFoundException
*/
public static Connection getConnection(String url, String username, String password){
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* Close a <code>Connection</code>, avoid closing if null.
*
* @param conn
* Connection to close.
* @throws SQLException
* if a database access error occurs
*/
public static void close(Connection conn) throws SQLException {
if (conn != null) {
conn.close();
}
}
/**
* Close a <code>Connection</code>, avoid closing if null and hide any
* SQLExceptions that occur.
*
* @param conn
* Connection to close.
*/
public static void closeQuietly(Connection conn) {
try {
close(conn);
} catch (SQLException e) { // NOPMD
// quiet
e.printStackTrace();
}
}
}
实现数据迁移的核心方法
@SuppressWarnings("all")
public class TableDao {
private static final Logger logger =LoggerFactory.getLogger(TableDao.class);
private static QueryRunner query = new QueryRunner();
private static String pksSql;
static {
try {
pksSql = Global.getConfig("pksSql");
} catch (Exception e) {
e.printStackTrace();
}
}
//pksSql = select cu.COLUMN_NAME from user_cons_columns cu, user_constraints au where cu.constraint_name = au.constraint_name and au.constraint_type = 'P' and au.table_name = ?
/**
* 根据表名获取表的主键列名
* @param tableName
* @return
*/
public static List<String> findPrimaryKeysByTableName(String tableName,ConnectionInfo info){
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
tableName = tableName.toUpperCase();
List<Object> primaryKeys = null;
List<String> pks = null;
try {
primaryKeys = query.query(conn,pksSql,new ColumnListHandler<>("column_name"),tableName);
pks = new ArrayList<>();
for (Object key : primaryKeys) {
String k = (String) key;
pks.add(k.toLowerCase());
}
return pks;
} catch (Exception e) {
logger.error("获取 {} 的主键列失败,sql为:{},原因:{}",tableName,pksSql,e.getMessage());
} finally {
DBTools.closeQuietly(conn);
}
return pks;
}
/**
* 根据表名查找该表的列的信息
* @param tableName
* @return
*/
public static List<ColumnInfo> findColumninfoListByTableName(String tableName,ConnectionInfo info) {
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
String columnsSql = "select * from user_tab_columns where table_name = ?";
List<ColumnInfo> list = null;
try {
BeanProcessor bean = new GenerousBeanProcessor();
RowProcessor processor = new BasicRowProcessor(bean);
list = query.query(conn, columnsSql, new BeanListHandler<ColumnInfo>(ColumnInfo.class,processor),
new Object[] { tableName.toUpperCase() });
} catch (SQLException e) {
logger.error("获取 {} 的列信息失败,sql为:{},错误:{}",tableName,columnsSql,e.getMessage());
} finally {
DBTools.closeQuietly(conn);
}
return list;
}
/**
* 根据表名查找到该表的所有字段的名称
* @param tableName
* @return
*/
public static List<String> findColumesNamesByTableName(String tableName,ConnectionInfo info){
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
String sql="select * from user_tab_columns where table_name = ?";
tableName = tableName.toUpperCase();
List<Object> columes = null;
List<String> cols = null;
try {
columes = query.query(conn, sql,new ColumnListHandler<>("column_name"),tableName);
cols = new ArrayList<>();
for (Object col : columes) {
String co = (String) col;
cols.add(co.toLowerCase());
}
return cols;
} catch (Exception e) {
logger.error("获取 {} 的字段名称失败,sql为{},错误为:{}",tableName,sql,e.getMessage());
} finally {
DBTools.closeQuietly(conn);
}
return cols;
}
/**
* 根据主键找到一条记录
* @param tableName
* @param pks
* @param params
* @return
*/
public static Map<String,Object> findOneMapByPrimaryKeys(String tableName,Object[] pks,Object[] params,ConnectionInfo info){
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
StringBuffer sb = new StringBuffer();
String sql = "select * from "+tableName;
sb.append(sql);
if(pks!=null && pks.length>0 && pks.length == params.length){
sb.append(" where ");
for (int i = 0; i < pks.length; i++) {
sb.append( pks[i] +" = ?");
if(i+1<pks.length){
sb.append(" and ");
}
}
}
try {
sql = sb.toString();
Map<String, Object> map = query.query(conn,sql, new MapHandler(),params);
logger.info("查询成功,主键为:{},sql语句:{}, 找到记录:{}",pks,sql,map);
return map;
} catch (Exception e) {
logger.error("查询失败,主键为:{}, sql语句:{},原因:{}",pks,sql,e.getMessage());
} finally {
DBTools.closeQuietly(conn);
}
return null;
}
/**
* 根据表名找到bean的list集合(废弃使用)
* @param tableName
* @param T
* @return
* @throws SQLException
*/
@Deprecated
public static <T> List<T> getTableBeanList(String tableName,Class<T> T,ConnectionInfo info) throws SQLException{
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
String sql = "select * from "+tableName;
List<T> beanList = new ArrayList<>();
beanList = (List<T>) query.query(conn,sql, new BeanListHandler<>(T));
return beanList;
}
/**
* 根据表名找到该表的所有数据
* @param tableName
* @return
*/
public static List<Map<String,Object>> findMapListByTableName(String tableName,ConnectionInfo info) {
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
String sql = "select * from "+tableName;
List<Map<String,Object>> mapList = null;
try {
mapList = query.query(conn,sql,new MapListHandler());
return mapList;
} catch (Exception e) {
logger.error("获取表的所有记录失败,sql:{},原因:{}",sql,e.getMessage());
} finally {
DBTools.closeQuietly(conn);
}
return mapList;
}
/**
* 插入记录
* @param tableName 表名
* @param map 字段和参数
*/
public static void insert(String tableName,Map<String,Object> map,ConnectionInfo info){
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
String sql = "insert into "+tableName+"(";
StringBuffer sb = new StringBuffer();
sb.append(sql);
Set<Entry<String,Object>> entrySet = map.entrySet();
Iterator<Entry<String, Object>> iterator = entrySet.iterator();
String cols = "";
String vals = "";
List<Object> params = new ArrayList<>();
while (iterator.hasNext()) {
Entry<String, Object> next = iterator.next();
cols += next.getKey()+",";
vals += " ?,";
params.add(next.getValue());
//去除最后的逗号
if(!iterator.hasNext()){
StringBuffer s1 = new StringBuffer(cols);
s1 = s1.deleteCharAt(s1.length()-1);
s1.append(")");
cols = s1.toString();
StringBuffer s2 = new StringBuffer(vals);
s2 = s2.deleteCharAt(s2.length()-1);
s2.append(")");
vals = s2.toString();
}
}
sb.append(cols).append(" values(").append(vals);
sql = sb.toString();
//执行
try {
query.update(conn,sql,params.toArray());
logger.info("插入数据成功,sql为:{}",sql);
logger.debug("参数为:{}",params);
} catch (Exception e) {
logger.error("插入数据失败,sql为:{}",sql);
logger.error("参数为:{}",params);
logger.error("失败原因:{}",e.getMessage());
} finally{
DBTools.closeQuietly(conn);
}
}
/**
* 插入操作,只适用于同一个数据库下(废弃使用)
* 例如: insert into tab1 select * from tab2 where ...
* @param destTable
* @param sourTable
* @param pks
* @param params
*/
@Deprecated
public static void directInsert(String destTable,String sourTable,String[] pks,Object[] params,ConnectionInfo info) {
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
StringBuffer sb = new StringBuffer();
String sql = "insert into "+ destTable+ " select * from "+ sourTable;
sb.append(sql);
if(pks!=null && pks.length>0 && pks.length == params.length){
sb.append(" where ");
for (int i = 0; i < pks.length; i++) {
if(params[i] instanceof Integer || params[i] instanceof Long ||params[i] instanceof Double){ //数字类型
sb.append( pks[i] +" = "+ params[i]);
} else if(params[i] instanceof String){ //字符串类型加单引号 ''
sb.append( pks[i] +" = "+ "'"+params[i]+"'");
} else{ //其他类型暂时不考虑,当字符串处理
sb.append( pks[i] +" = "+ "'"+params[i]+"'");
}
if(i+1<pks.length){
sb.append(" and ");
}
}
}
sql = sb.toString();
try {
query.update(conn,sql);
logger.info("插入目标表:{} 成功,插入的sql语句为:{}",destTable,sql);
} catch (Exception e) {
logger.error("插入目标表:{} 失败,插入的sql语句为:{}",destTable,sql);
logger.error("失败原因:{}",e.getMessage());
}
}
/**
* 删除记录
* @param tableName 表名
* @param pks 主键
* @param params 参数
*/
public static void delete(String tableName,Object[] pks,Object[] params,ConnectionInfo info){
if(pks.length == 0){
logger.error("删除指定数据请声明条件");
return;
}
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
StringBuffer sb = new StringBuffer();
String sql = "delete from "+tableName+" where ";
sb.append(sql);
if(pks!=null && pks.length>0 && pks.length == params.length){
for (int i = 0; i < pks.length; i++) {
sb.append( pks[i] +" = ?");
if(i+1<pks.length){
sb.append(" and ");
}
}
}
sql = sb.toString();
try {
query.update(conn, sql,params);
logger.info("删除指定记录成功,sql为:{}",sql);
logger.debug("参数为:{}",Arrays.toString(params));
} catch (Exception e) {
logger.error("删除指定记录失败,sql为:{},原因:{}",sql,e.getMessage());
logger.error("参数为:{}",Arrays.toString(params));
} finally{
DBTools.closeQuietly(conn);
}
}
/**
* 更新记录
* @param tableName 要更新表的名字
* @param pks 主键
* @param otherCols 除主键外的要更新的列名
* @param params 要修改的字段的值和主键的值,按顺序排列
*/
public static void update(String tableName,Object[] pks,Object[] otherCols,Object[] params,ConnectionInfo info){
if(pks.length == 0 || otherCols.length == 0 || pks.length+otherCols.length != params.length){
logger.error("参数不规范: 无主键,无修改的字段,参数个数不对");
return;
}
Connection conn = DBTools.getConnection(info.getJdbcUrl(), info.getUserName(), info.getPdwrod());
String sql = "update "+tableName+" set ";
StringBuffer sb = new StringBuffer();
sb.append(sql);
for (int i = 0; i < otherCols.length; i++) {
sb.append(otherCols[i] + " = ?");
if(i+1<otherCols.length){
sb.append(",");
}
}
sb.append(" where ");
for (int i = 0; i < pks.length; i++) {
sb.append(pks[i] + " =?");
if(i+1<pks.length){
sb.append(" and ");
}
}
sql = sb.toString();
//执行
try {
query.update(conn,sql,params);
logger.info("表名:{},更新成功:",tableName);
logger.debug("sql为:{}",sql);
logger.debug("参数为:{}",params);
} catch (Exception e) {
logger.error("表名:{},更新失败; 原因:{}",tableName,e.getMessage());
logger.error("sql为:{}",sql);
logger.error("参数为:{}",Arrays.toString(params));
} finally{
DBTools.closeQuietly(conn);
}
}
/**
* MD5加密
* @param str 加密字符串
* @return
* @throws Exception
*/
public static String getMD5(String str){
MessageDigest md5 = null;
byte[] byteArray = null;
try {
md5 = MessageDigest.getInstance("MD5");
byteArray = str.getBytes("UTF-8");
} catch (Exception e) {
logger.error("获取MD5密文失败!!!");
return "1"; //返回相同的密文,防止数据更新错误
}
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString();
}
/**
* 返回为Map,进行比较
* @param masterListMap
* @param slaveListMap
*/
public static void updateTbDataByinsert2(List<Map<String,Object>> masterListMap,List<Map<String,Object>> slaveListMap){
}
public static void updateTbDataByDelete2(List<Map<String,Object>> masterListMap,List<Map<String,Object>> slaveListMap){
}
public static void updateTbDataByUpdate2(List<Map<String,Object>> masterListMap,List<Map<String,Object>> slaveListMap){
}
}
测试数据库同步
/**
* 启动主程序
*
* @author zhoucl
*
*/
public class StartupMain {
private static Logger logger = LoggerFactory.getLogger(StartupMain.class);
private static final String TABLE_MASTER = "TB_BASE_COUNTRY_INFO";
private static final String TABLE_SLAVE = "TB_BASE_COUNTRY_INFO";
private static final String MD5_KEY_NAME = "md5_record_value";
public static void main(String[] args) throws Exception {
//取主表所有记录成list hashmap
//对记录进行for循环,将所有的数据计算MD5值(规则是所有)A=DDD,DSF=DSF,DF=DSF,SDF,再计算MD5值 方法DigestUtils.md5Hex,然后修改haspmap
//增加一个KEY进去,KEY的名字叫MD5_RECORD_VALUE(这个名字定义成常量)
//比较两个list,找出需要新增的数据,找出需要修改数据,找出需要删除的记录
//FOR循环 insert 新增的数据
//FOR循环 update 修改数据
//FOR循环 delete 删除的记录
ConnectionInfo slaveConnection = new ConnectionInfo();
slaveConnection.setJdbcUrl("jdbc:oracle:thin:@112.74.163.78:1521:xe");
slaveConnection.setUserName("system");
slaveConnection.setPdwrod("oracle");
ConnectionInfo masterConnection = new ConnectionInfo();
masterConnection.setJdbcUrl("jdbc:oracle:thin:@112.74.163.97:1521:xe");
masterConnection.setUserName("system");
masterConnection.setPdwrod("oracle");
//获取主表的主键
List<String> pksMaster = TableDao.findPrimaryKeysByTableName(TABLE_MASTER,masterConnection);
//获取从表的主键
List<String> pksSlave = TableDao.findPrimaryKeysByTableName(TABLE_SLAVE,slaveConnection);
//获取主表的所有列名
List<String> colsMaster = TableDao.findColumesNamesByTableName(TABLE_MASTER,masterConnection);
//获取从表的所有列名
List<String> colsSlave = TableDao.findColumesNamesByTableName(TABLE_SLAVE,slaveConnection);
//除主键外的其他字段的列名
List<String> otherCols = TableDao.findColumesNamesByTableName(TABLE_MASTER,masterConnection);
otherCols.removeAll(pksMaster);
//判断主键是否相同
boolean isPksSame = isListSame(pksMaster,pksSlave);
//判断字段
boolean isColsSame = isListSame(colsMaster,colsSlave);
if(!isPksSame){
throw new RuntimeException("{"+TABLE_MASTER+"},{"+TABLE_SLAVE+"} 两张表的主键不一致,请查看");
}
if(!isColsSame){
throw new RuntimeException("{"+TABLE_MASTER+"},{"+TABLE_SLAVE+"} 两张表的字段不一致,请查看");
}
logger.info("========================================调整MAP结构=======================================");
//获取主表的所有数据
List<Map<String, Object>> masterMapList = TableDao.findMapListByTableName(TABLE_MASTER,masterConnection);
masterMapList = reConstructMap(masterMapList);
//获取从表的所有数据
List<Map<String, Object>> slaveMapList = TableDao.findMapListByTableName(TABLE_SLAVE,slaveConnection);
slaveMapList = reConstructMap(slaveMapList);
logger.info("========================================MAP结构设置完毕=======================================");
logger.info("==========================================开始更新=========================================");
//待插入的数据集合
List<Map<String,Object>> needInsertMapList = new ArrayList<>();
//待更新的数据集合
List<Map<String,Object>> needUpdateMapList = new ArrayList<>();
//待删除的数据集合
List<Map<String,Object>> needDeleteMapList = new ArrayList<>();
//找到需要插入和更新的数据
findNeedInsertAndUpdateData(masterMapList,slaveMapList,needInsertMapList,needUpdateMapList,pksMaster);
//找到需要删除的数据
findNeedDeleteMapList(masterMapList,slaveMapList,needDeleteMapList,pksSlave);
//批量新增
batchInsert(needInsertMapList,slaveConnection);
//批量删除
batchDelete(needDeleteMapList, pksSlave,slaveConnection);
//批量更新
batchUpdate(needUpdateMapList, otherCols, pksSlave,slaveConnection);
logger.info("==========================================更新完毕==========================================");
}
/**
* 批量插入
* @param tableName
* @param mapList
*/
public static void batchInsert(List<Map<String,Object>> mapList,ConnectionInfo conn){
for (int i = 0,size=mapList.size();i < size; i++) {
Map<String, Object> map = mapList.get(i);
logger.info("正在插入,总的记录数:{},进度:[{}/{}],当前插入数据:{}",size,i+1,size,map);
map.remove(MD5_KEY_NAME);
TableDao.insert(TABLE_SLAVE, map,conn);
}
}
/**
* 批量删除
* @param mapList
* @param pks
*/
public static void batchDelete(List<Map<String,Object>> mapList,List<String> pksSlave,ConnectionInfo conn){
for (int i = 0,size=mapList.size();i < size; i++) {
Map<String, Object> map = mapList.get(i);
logger.info("正在删除,总的记录数:{},进度:[{}/{}],当前删除数据:{}",size,i+1,size,map);
map.remove(MD5_KEY_NAME);
Object[] slavePkValues = getValueToPks(pksSlave,map);
TableDao.delete(TABLE_SLAVE, pksSlave.toArray(), slavePkValues,conn);
}
}
/**
* 批量更新
* @param mapList
* @param otherCols
* @param slavePks
*/
public static void batchUpdate(List<Map<String,Object>> mapList,List<String> otherCols,List<String> pksSlave,ConnectionInfo conn){
//TableDao.update(tableName, pks, otherCols, params);
for (int i = 0,size=mapList.size();i < size; i++) {
Map<String, Object> map = mapList.get(i);
logger.info("正在更新,总的记录数:{},进度:[{}/{}],当前插入数据:{}",size,i+1,size,map);
Object[] pkValues = getValueToPks(pksSlave,map);
List<Object> params = new ArrayList<>();
for (int j = 0, num=otherCols.size();j < num; j++) {
String col = otherCols.get(j);
Object val = map.get(col);
params.add(val);
}
for (int z = 0; z < pkValues.length; z++) {
Object obj = pkValues[z];
params.add(obj);
}
//更新
TableDao.update(TABLE_SLAVE, pksSlave.toArray(), otherCols.toArray(), params.toArray(),conn);
}
}
/**
* 找到需要插入和更新的map
* @param masterMapList 主表数据集合
* @param slaveMapList 从表数据集合
* @param needInsertMapList 待插入数据集合
* @param needUpdateMapList 待更新数据集合
* @param pksMaster 主键列表
*/
public static void findNeedInsertAndUpdateData(List<Map<String,Object>> masterMapList,List<Map<String,Object>> slaveMapList,
List<Map<String,Object>> needInsertMapList,List<Map<String,Object>> needUpdateMapList,
List<String> pksMaster){
for (int i = 0,size=masterMapList.size(); i < size; i++) {
Map<String, Object> masterMap = masterMapList.get(i);
String masterMd5 = (String) masterMap.get(MD5_KEY_NAME);
Object[] masterPkValues = getValueToPks(pksMaster,masterMap);
boolean flag = false;
for (int j = 0,num=slaveMapList.size(); j < num; j++) {
Map<String, Object> slaveMap = slaveMapList.get(j);
Object[] slavePkValues = getValueToPks(pksMaster,slaveMap);
if(Arrays.equals(masterPkValues, slavePkValues)){
flag = true; //找到了相同的主键
//判断MD5的值
String slaveMd5 = (String) slaveMap.get(MD5_KEY_NAME);
if(masterMd5.equals(slaveMd5)){
break;
}else{
//放入待更新数据集合
needUpdateMapList.add(masterMap);
}
}
}
if(!flag){ //遍历了一轮还没有找到相同的主键的值,放入待新增数据
needInsertMapList.add(masterMap);
}
}
logger.info("==============================待插入数据条数:{}",needInsertMapList.size());
logger.info("==============================待更新数据条数:{}",needUpdateMapList.size());
}
/**
* 找到需要删除的map
* @param masterMapList 主表数据集合
* @param slaveMapList 从表数据集合
* @param needDeleteMapList 待删除数据集合
* @param pksSlave 主键列表
*/
public static void findNeedDeleteMapList(List<Map<String,Object>> masterMapList,List<Map<String,Object>> slaveMapList,
List<Map<String,Object>> needDeleteMapList,List<String>pksSlave){
for (int i = 0,size=slaveMapList.size();i<size; i++) {
Map<String, Object> slaveMap = slaveMapList.get(i);
Object[] slavePkValuees = getValueToPks(pksSlave,slaveMap);
boolean flag = false;
for (int j = 0,num=masterMapList.size(); j < num; j++) {
Map<String, Object> masterMap = masterMapList.get(j);
Object[] masterPkValues = getValueToPks(pksSlave,masterMap);
if(Arrays.equals(slavePkValuees, masterPkValues)){
flag = true;
break;
}
}
if(!flag){ //如果主表中根据主键没有找到数据,放入待删除数据
needDeleteMapList.add(slaveMap);
}
}
logger.info("==============================待删除数据条数:{}",needDeleteMapList.size());
}
/**
* 为每条记录添加md5字段
* @param msaterMapList 主表或从表数据集合
* @return
*/
public static List<Map<String, Object>> reConstructMap(List<Map<String, Object>> msaterMapList) {
for (int i = 0, size =msaterMapList.size() ; i < size; i++) {
Map<String, Object> map = msaterMapList.get(i);
StringBuffer sb = new StringBuffer();
Set<Entry<String,Object>> entrySet = map.entrySet();
Iterator<Entry<String, Object>> it = entrySet.iterator();
sb.append("{");
while(it.hasNext()){
Entry<String, Object> entry = it.next();
sb.append(entry.getKey()+" = ");
sb.append(String.valueOf(entry.getValue()));
if(it.hasNext()){
sb.append(", ");
}
}
sb.append("}");
String jsonInfo = sb.toString();
// logger.info("json为:{}",jsonInfo);
//对字符串加密
String md5Hex = DigestUtils.md5Hex(jsonInfo);
map.put(MD5_KEY_NAME, md5Hex);
// logger.info("map为:{}",map);
}
return msaterMapList;
}
/**
* 获取Key加载信息
*/
public static void printKeyLoadMessage() {
StringBuilder sb = new StringBuilder();
sb.append("\r\n======================================================================\r\n");
sb.append("\r\n 欢迎使用小表广播程序 \r\n");
sb.append("\r\n======================================================================\r\n");
System.out.println(sb.toString());
}
/**
* 判断主键,cols是否相同
* @param pks1
* @param pks2
* @return
*/
private static boolean isListSame(List<?> pks1,List<?> pks2){
if(pks1 != null && pks2 != null){
if(pks1.containsAll(pks2) && pks2.containsAll(pks1)){
return true;
}
}
logger.warn("list1:{}",pks1);
logger.warn("list2:{}",pks2);
return false;
}
/**
* 用于获取每条记录主键的值
* @param pks 主键列表
* @param map
* @return
*/
public static Object[] getValueToPks(List<String> pks, Map<String, Object> map) {
List<Object> values = new ArrayList<>();
for (String pk : pks) {
Object val = map.get(pk);
values.add(val);
}
return values.toArray();
}
}