开发中遇到的一些问题与解决思路(1)

前言

整理了一下开发中遇到的一些问题及解决思路,特地分享下。

正文

Mysql临时变量(局部变量)的使用

Mysql临时变量又称为局部变量,其带有@符号,使用时不需要声明,只在局部起作用。

用法

  • set @num=1; 或set @num:=1;

    这里要使用变量来保存数据,直接使用@num变量即可

  • select @num:=1; 或 select @num:=字段名 from 表名 where ……

注意上面两种赋值符号,使用set时可以用“=”或“:=”,但是使用select时必须用“:=赋值”

巧妙的使用临时变量,可以解决我们遇到的一些比较棘手的问题。

例子

假设有一张客户表,customer ,其有字段 id(表示唯一id),字段 locale (表示国家,0001 中国,0002 美国,0003 日本 等),字段 create_time 表示这个用户的创建日期(yyyy-MM-dd hh:MM:ss)形式,等其他字段。

现在这张表已经上线一段时间,有了大量数据,由于某些原因(需求),需要新增字段 客户号 customer_no ,客户号的生成遵循一定规则,比如 2018-11-12 日当天第一个中国用户,则客户号为 CN201811120001 这种格式,第20个中国用户客户号为 CN201811120020,即用户号的生成规则是 国家简称 + 日期yyyyMMdd + 0001 递增,当天第一个美国用户客户号为 AM201811120001 这种形式,如果日期变为2018-11-13 则这一天的第一个中国用户为 CN201811130001 这种形式。

这儿就不讨论客户号之后的生成逻辑,现在这张表已经有一定量的数据,新增了客户号字段,需要维护旧数据的此字段,手动修改显然是不现实的……

这时候我们可以借助临时变量来实现对旧数据的更新维护,locale为中国(CN)的代码更新可以如下(其它国家类似):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
-- 临时自增变量
SET @rownum = 0;
-- 初始值,需要手动设置,选择一个日期
SET @startdate = '20181227';
-- 更新语句
UPDATE customer p
SET p.customer_no = (
SELECT
b.num
FROM
(
SELECT
CASE DATE_FORMAT(a.create_time, "%Y%m%d")
WHEN @startdate THEN
@rownum :=@rownum + 1
ELSE
@rownum := 1
END as temp,
CONCAT(
'CN',
DATE_FORMAT(a.create_time, "%Y%m%d"),
CASE
WHEN @rownum > 999 THEN
''
WHEN @rownum > 99 THEN
'0'
WHEN @rownum > 9 THEN
'00'
ELSE
'000'
END,
@rownum
) AS num,
(
CASE DATE_FORMAT(a.create_time, "%Y%m%d")
WHEN @startdate THEN
@startdate
ELSE
@startdate := DATE_FORMAT(a.create_time, "%Y%m%d")
END
) AS helpdata,
a.id AS id
FROM
customer a
WHERE
1 = 1 ORDER BY p.create_time asc
) AS b
WHERE
b.id = p.id
)
WHERE
p.locale = '0001';

可以看到这个SQL超级长,其实不算复杂,其customer_no的赋值就是借助了两个临时变量进行的,可以看到第一个**@rownum**临时变量,用来进行自增,拼接为0001、0010等等这种形式。

为什么还有个**@startdate**临时变量呢?

这个变量主要用来记录上一条的时间跟要更新的这一条的时间是否一致,一致的话**@rownum自增1,不一致说明是新的时间,@rownum重新从1开始,这样做的前提保证是数据要根据create_time**进行排序,即 ORDER BY p.create_time asc的作用。

同时我们还可以看到我们借助了中间表b实现了对**@startdate**的赋值。

其实主要思想可以如下分解,更新表里的旧数据,需要将customer_no赋值,则可以先写一个select语句查询出 CN201811120001 这种形式的一张表,通过b.id = p.id实现关联到指定的要更新的数据,然后进行数据update。

这属于一个较复杂的Mysql临时变量的使用例子,临时变量虽然在Mysql查询等语句中使用的不多,但是关键时候还是有很大作用的。

使用并行流时,必须保证操作对象的线程安全性

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Exception{
//构造一个源数据List
List<Integer> list = new ArrayList<>();
for(int i = 0;i<100000;i++){
list.add(i);
}
//线程同步的ArrayList
List<Integer> list1 = Collections.synchronizedList(new ArrayList<>());
//普通的ArrayList
List<Integer> list2 = new ArrayList<>();
//并行流处理
list.parallelStream().forEach(e->{
list1.add(e);
list2.add(e);
});
//输出结果
System.out.println(list1.size());
System.out.println(list2.size());
}

输出结果:

1
2
100000
99785

可以看到list2 数据不正常,多运行几次,可以发现list2有时候正常,有时候不正常,有时候甚至会出现数组下标越界异常等情况。

而list1的数据结果一直没有问题。

所以在操作ArrayList的不安全操作时(或者其他不安全数据结构),如add,delete等,如果使用并行流,必须保证被操作对象的线程安全性。

上述情况一般有两种解决办法:

  1. 使用线程安全的对象,如ArrayList使用Collections.synchronizedList 方法变为同步的List,或者使用Vector等。
  2. 抛弃并行流,使用串行流或者其他解决办法。

Java8 lambda表达式无法抛出受检查异常的问题

我们知道Java异常分为两类,受检查异常(Checked Exception)跟非受检异常(UnChecked Exception)。

对于受检查异常,代码中必须显式处理该异常,不然编译不通过,如IOException等。

而对于非受检查异常,或称为运行时异常,可以不用处理,如RunTimeException等。

对于一个方法,如果方法内部抛出受检查异常,则方法本身也要显式抛出异常。

我们来看下下面两个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.stream().forEach(e->{
throw new RuntimeException("Exception");
});
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.stream().forEach(e->{
throw new IOException("Exception");
});
}

可以看到第二个main方法是编译不通过的,因为抛出了受检查异常,而它lambda表达式如下

1
Consumer consumer = e->{throw new IOException("Exception");};

由于Consumer执行的accept方法,如下:

1
2
3
4
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

故需要该方法抛出受检查异常才可以,显然我们是无法修改Java源码的。

对于其它函数式接口lambda表达式亦是如此。

那如何处理这种情况呢?

在程序代码想使用lambda表达式的情况下,大致有如下两种办法:

  1. 将受检查异常包装成非受检查异常。

    1
    2
    3
    list.stream().forEach(e->{
    throw new RuntimeException(new IOException("Exception"));
    });

    这样虽然解决了问题,但是破坏了异常结构,代码也不是很美观,在涉及到一些事务的方法上,还会导致不能正确捕捉异常进行回滚而产生一些问题等。

    不推荐使用。

  2. 包装泛型异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("1");
    list.stream().forEach(e->{
    doThrow(new IOException());
    });
    }
    public static <E extends Exception> void doThrow(Exception e) throws E {
    throw (E)e;
    }

    可以看到我们利用异常泛型包装了异常处理,这时候编译器不能明确异常类型,因此编译通过。

    这时候我们在测试,可以看到lambda表达式运行后抛出了我们期望的IOException。

结语

以后再有开发相关方面的问题,值得记录和分享的,我会在更新。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道