赞
踩
一次业务场景需要,得把一个数据库中的数据导入另一个数据库,2个数据库类型不同,当时采用的方式为:
把数据库A的表中的数据导出成dat文件(这个数据库导出的文件就是dat文件),一行一条记录,字段顺序按照建表字段顺序,各个字段中间用欧元符号分隔,在数据库B中建表,表结构与数据库中的表结构完全一致,最后再增加一个ids自增字段。dat文件存放在文件服务器指定目录,程序通过ssh连接服务器,并获取dat文件流来获取文件,一行一行读取文件内容然后批量入B库。文件名称中的编码字符可以确定这个文件入哪个表。持久层框架用的是mybatis。
总共十几+个表,表的字段也都很多,既然文件格式有约定,那入B库时的sql不计划指定字段插入,太多太麻烦。
- #指定字段插入数据
- insert into tableB (field1,field2) values (#{i1},#{i2});
- #不指定字段插入数据,需要按顺序给出全部字段的值
- insert into tableB values (#{i1} ... );
最终处理流程是:
1.通过BufferedReader的readLine()方法一行一行读数据;
2.将读出的数据按照字段分隔符(此处为€)分隔成字符串数组,再将字符串数组转成List line,再用ArrayList 包装一下;
List lineList=new ArrayList(Arrays.asList(line.split(dataFileSeparator)));
那么这个lineList里的数据就为一行数据的各个字段值。
为什么要用ArrayList再包装下呢?因为Arrays.asList()返回的对象是 Arrays的内部类,继承了AbstractList,AbstractList中的set, add, remove 抽象方法中都是直接throw new UnsupportedOperationException(); 但内部类 ArrayList 并未重写父类的 add,remove方法,所以我们不能再对存放各个字段值的List不能增加删除,如果不涉及增加其他字段值或删除字段值的操作,可以直接使用,不用包装。对Arrays.asList 再包装一层,生成的List对象就是我们熟知的 ArrayList了。
3.在循环外层定义一个ArrayList datas,用来存放一行一行数据的List lineList;
4.将datas作为参数传给mapper中的入库方法,xml这么写的:
- <insert id="insertModelData">
- insert into ${tableName} values
- <foreach collection="list" item="line" separator=",">
- (
- <foreach collection="line" item="item" separator=",">
- #{item}
- </foreach>
- )
- </foreach>
- </insert>
表名根据文件名称映射,第一层foreach取出一行一行的所有数据,第二层foreach取出每行的各个字段。
就这样,不用写一大堆sql,一个方法就可以满足此任务的所有表的数据入库操作。但是要注意${}、#{}的区别,有需要要额外处理${}字段。
此任务中其他需要注意的问题:
1.BufferedReader的readLine()读出的字符串中不包含结尾的"\r","\n"
2.BufferedReader的readLine()相关源码如下:
- public String readLine() throws IOException {
- return readLine(false);
- }
- String readLine(boolean ignoreLF) throws IOException {
- StringBuffer s = null;
- int startChar;
-
- synchronized (lock) {
- ensureOpen();
- boolean omitLF = ignoreLF || skipLF;
-
- bufferLoop:
- for (;;) {
-
- if (nextChar >= nChars)
- fill();
- if (nextChar >= nChars) { /* EOF */
- if (s != null && s.length() > 0)
- return s.toString();
- else
- return null;
- }
- boolean eol = false;
- char c = 0;
- int i;
-
- /* Skip a leftover '\n', if necessary */
- if (omitLF && (cb[nextChar] == '\n'))
- nextChar++;
- skipLF = false;
- omitLF = false;
-
- charLoop:
- for (i = nextChar; i < nChars; i++) {
- c = cb[i];
- if ((c == '\n') || (c == '\r')) {
- eol = true;
- break charLoop;
- }
- }
-
- startChar = nextChar;
- nextChar = i;
-
- if (eol) {
- String str;
- if (s == null) {
- str = new String(cb, startChar, i - startChar);
- } else {
- s.append(cb, startChar, i - startChar);
- str = s.toString();
- }
- nextChar++;
- if (c == '\r') {
- skipLF = true;
- }
- return str;
- }
-
- if (s == null)
- s = new StringBuffer(defaultExpectedLineLength);
- s.append(cb, startChar, i - startChar);
- }
- }
- }
可以看出该方法可以设置跳过LF,但是该方法访问权限为default,不支持继承该类重写方法,而且其他方法也不支持我们设置修改变量,所以我们没办法控制readLine()是读到\r 换行还是 \n换行还是\r\n换行,我们没法指定特定换行符来让它读一行,所以文件中有^M时需要留意,有可能它读了半行就返回了。
在windows平台下,换行符是\r\n,而在linux下是\n,这多出来的\r被vim解释成了^M。
windows linux MacBook 换行符 \r\n \n \r ASCII 0x0d0a 0x0a 0x0d 其中:
- "\r"在ASCII中表示“换行(LF)”
- "\n"在ASCII中表示“回车(CR)”
访问控制修饰符 private default protected public 同一类中的成员 √ √ √ √ 同一包中其他类 √ √ √ 不同包中的子类 √ √ 不同包中非子类 √
笔者当时就遇到^M这个问题,返回半行,入库时mysql报字段不匹配的错误,当然了,少字段嘛。当时时间紧而且是临时需求 ,过一段时间就不用了,所以没有通过程序解决,采用的方式是替换文件中的^M符号。通过程序的话貌似得用read方法,这个就有点复杂了,到指定的换行符才停,逻辑有点小复杂,没时间呀~这里mark一下,有空研究,也欢迎有经验的朋友分享心得。
以下内容来自:Linux下去掉^M的4种方法
第一种方法:
cat -A filename
就可以看到windows下的断元字符 ^M要去除他,最简单用下面的命令:
dos2unix filename
第二种方法:
1
2
sed
-i ‘s/^M
//g
' filename
#注意:^M的输入方式是 Ctrl + v ,然后Ctrl + M
第三种方法:
1
2
3
#vi filename
:1,$ s/^M
//g
^M 输入方法: ctrl+V ,ctrl+M
第四种方法:
1
2
#cat filename |tr -d ‘/r' > newfile
#^M 可用 /r 代替
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。