Home 实习

0 72

如果你问我,实习期间最紧张最害怕的时刻是什么?

那一定是上线发版的时候。

 

年少轻狂三连击,服务全挂我挨批

公司的服务采用集群部署,发版时需要部署多台机器。当我第一次发版前,导师曾再三叮嘱我,项目发慢点,一台一台的发。那时我年少轻狂,觉得呆呆等着很无聊,于是一个三连击,仅有的三台机器同时发布,结果服务调用异常的报错铺天盖地而来

服务器部署服务的流程

服务部署在机器上,那么该机器就成了服务器,所有的用户请求都会发送到服务器上被处理。而当前互联网环境下,服务基本都是分布式/集群部署,也就是有多台服务器。各互联网企业在分布式/集群部署的环境下发布版本时,一定不会像我一样三连击,因为服务器部署服务的流程如下:

旧服务正在运行—>停掉旧服务—>部署新服务—>新服务启动

试想,三台服务器A,B,C工作的好好的,我先点击发布A机器,此时A机器先向注册中心发送消息——”我先走了哈,别把请求路由到我这里了”,然后Dubbo会将A机器从负载均衡策略中排除,如果A机器还有未处理的请求,它会先进行处理,之后服务彻底停止。(详细可参考Dubbo-优雅停机)停止后,A机器会部署新服务,也就是你发布的新版本,但是服务不是瞬间启动的它需要启动服务提供者,服务提供者在启动时还要向注册中心注册自己提供的服务,同时订阅自己需要的服务,等完成这些步骤后,A机器上的新服务才算是正式启动,才可以接收用户的请求(详细可参考Dubbo的简要执行流程)。显而易见,【停掉旧服务—>新服务启动】之间是需要耗费不少时间的,大概几分钟。

三连击为什么会导致服务挂掉呢?

原因就是 【停掉旧服务—>新服务启动】的这几分钟。A机器的服务还在启动中,此时A服务器不可用,而我又点击发布B机器,B机器的服务也进入了【停掉旧服务—>新服务启动】这个阶段,B服务器不可用,C机器同理。三连击导致A,B,C三台机器全部不可用,用户请求一过来就会报服务调用异常的错误

正确的流程应该是发布一台,观察一台的日志。A机器发布后,观察A机器的日志,确保A机器上的服务已经启动且有流量进入,这样的话不仅可以保证A机器可用,如果你的新版本出现了问题还可以立刻发现,此时就算需要回滚也只会影响A机器上的请求,B,C机器的服务是不受影响的,当然,最好的情况是任意一台机器都没有事故。

之所以服务器要进行分布式/集群部署,正是为了避免服务不可用。试想,如果淘宝618时用户无法下单,短短的几分钟可能会造成数以亿计的损失。所以一定要牢记,服务不可用是非常严重的事故。因此,涉及到集群环境下的服务部署,我们必须要考虑可用性及可靠性。

一台一台的发,我有一千台机器岂不是要发好几天?

非也,此处的一台一台发,强调的是一个百分比。我们知道,客户端请求会通过Dubbo的负载均衡策略选择路由到哪一台具体的服务器,如果你有A,B,C三台机器提供服务,一台一台的发可以保证任意时刻有2/3的机器可用,只要2/3的机器可以顶住当前的客户端流量,那就没有问题。(当然,如果2/3的机器顶不住,那你必须要加机器,不然的话流量会击垮B,C两台机器,服务彻底不可用)如果你有一千台机器,你完全可以一批10台或者20台的去发,尽管有10台或20台的机器不可用,但仍剩有99%或98%的机器可用,只要剩下的99%的机器可以顶得住当前流量,那就一点问题都没有。

一句话,多少台机器一起发,取决于发布时剩下可用的机器能否顶得住当前客户端流量。顶得住,没有任何问题;大大顶得住,甚至可以加大同时发布的机器数量,比如1000台只需要500台就足够顶住流量的话,直接50台50台的发布;顶不住的话,对不起,你还是老老实实的减少同时发布的机器数量吧~。

Dubbo-优雅停机

背景

对于任何一个线上应用,如何在服务更新部署过程中保证客户端无感知是开发者必须要解决的问题,即从应用停止到重启恢复服务这个阶段不能影响正常的业务请求。理想条件下,在没有请求的时候再进行更新是最安全可靠的,然而互联网应用必须要保证可用性,因此在技术层面上优化应用更新流程来保证服务在更新时无损是必要的。

传统的解决方式是通过将应用更新流程划分为手工摘流量、停应用、更新重启三个步骤,由人工操作实现客户端无对更新感知。这种方式简单而有效,但是限制较多:不仅需要使用借助网关的支持来摘流量,还需要在停应用前人工判断来保证在途请求已经处理完毕。这种需要人工介入的方式运维复杂度较高,只能适用规模较小的应用,无法在大规模系统上使用。

因此,如果在容器/框架级别提供某种自动化机制,来自动进行摘流量并确保处理完以到达的请求,不仅能保证业务不受更新影响,还可以极大地提升更新应用时的运维效率。

这个机制也就是优雅停机,目前Tomcat/Undertow/Dubbo等容器/框架都有提供相关实现。下面给出正式一些的定义:优雅停机是指在停止应用时,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等,优雅停机可以避免非正常关闭程序可能造成数据异常或丢失,应用异常等问题。优雅停机本质上是JVM即将关闭前执行的一些额外的处理代码。

适用场景

  • JVM主动关闭(System.exit(int)
  • JVM由于资源问题退出(OOM);
  • 应用程序接受到SIGTERMSIGINT信号。

配置方式

服务的优雅停机

在Dubbo中,优雅停机是默认开启的,停机等待时间为10000毫秒。可以通过配置dubbo.service.shutdown.wait来修改等待时间。

例如将等待时间设置为20秒可通过增加以下配置实现:

dubbo.service.shutdown.wait=20000

容器的优雅停机

当使用org.apache.dubbo.container.Main这种容器方式来使用 Dubbo 时,也可以通过配置dubbo.shutdown.hooktrue来开启优雅停机。

通过QOS优雅上下线

基于ShutdownHook方式的优雅停机无法确保所有关闭流程一定执行完,所以 Dubbo 推出了多段关闭的方式来保证服务完全无损。

多段关闭即将停止应用分为多个步骤,通过运维自动化脚本或手工操作的方式来保证脚本每一阶段都能执行完毕。

在关闭应用前,首先通过 QOS 的offline指令下线所有服务,然后等待一定时间确保已经到达请求全部处理完毕,由于服务已经在注册中心下线,当前应用不会有新的请求。这时再执行真正的关闭(SIGTERM 或 SIGINT)流程,就能保证服务无损。

QOS可通过 telnet 或 HTTP 方式使用,具体方式请见Dubbo-QOS命令使用说明

流程

Provider在接收到停机指令后

  • 从注册中心上注销所有服务;
  • 从配置中心取消监听动态配置;
  • 向所有连接的客户端发送只读事件,停止接收新请求;
  • 等待一段时间以处理已到达的请求,然后关闭请求处理线程池;
  • 断开所有客户端连接。

Consumer在接收到停机指令后

  • 拒绝新到请求,直接返回调用异常;
  • 等待当前已发送请求执行完毕,如果响应超时则强制关闭连接。

当使用容器方式运行 Dubbo 时,在容器准备退出前,可进行一系列的资源释放和清理工。

例如使用 SpringContainer时,Dubbo 的ShutdownHook线程会执行ApplicationContextstopclose方法,保证 Bean的生命周期完整。

实现原理

Dubbo 优雅停机 | Apache Dubbo

 

Dubbo的简要启动流程

1. 服务器启动,运行服务提供者。

2. 服务提供者在启动时,向注册中心(zookeeper)注册自己提供的服务。

3. 服务消费者在启动时,向注册中心订阅自己所需的服务。

4. 注册中心返回服务提供者地址列表给消费者,(若有变更,注册中心将基于长连接推送变更数据给消费者)

5. 服务的消费者,从地址列表中,基于负载均衡,选一台提供者的服务器进行调用,若是失败,在从 地址列表中,选择另一台调用.

6. 期间Dubbo的监控中心,会记录定时消费者和提供者,的调用次数和时间

Dubbo的简要执行流程 – 简书 (jianshu.com)

0 79

常见的对象属性拷贝方式解析

一、常见的对象属拷贝方式

  1. Apache BeanUtils

  2. Apache ProrertyUtils

  3. Springframework BeanUtils

  4. Cglib BeanCopier

二、性能对比

private static void testCglibBeanCopier(OriginObject origin, int len) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        System.out.println();
        System.out.println("================cglib BeanCopier执行" + len + "次================");
        DestinationObject destination3 = new DestinationObject();

        for (int i = 0; i < len; i++) {
            BeanCopier copier = BeanCopier.create(OriginObject.class, DestinationObject.class, false);
            copier.copy(origin, destination3, null);
        }
        stopwatch.stop();

        System.out.println("testCglibBeanCopier 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
    }

    private static void testApacheBeanUtils(OriginObject origin, int len)
            throws IllegalAccessException, InvocationTargetException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        System.out.println();
        System.out.println("================apache BeanUtils执行" + len + "次================");
        DestinationObject destination2 = new DestinationObject();
        for (int i = 0; i < len; i++) {
            BeanUtils.copyProperties(destination2, origin);
        }

        stopwatch.stop();

        System.out.println("testApacheBeanUtils 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
    }

    private static void testSpringFramework(OriginObject origin, int len) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        System.out.println("================springframework执行" + len + "次================");
        DestinationObject destination = new DestinationObject();

        for (int i = 0; i < len; i++) {
            org.springframework.beans.BeanUtils.copyProperties(origin, destination);
        }

        stopwatch.stop();

        System.out.println("testSpringFramework 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
    }

    private static void testApacheBeanUtilsPropertyUtils(OriginObject origin, int len)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        System.out.println();
        System.out.println("================apache BeanUtils PropertyUtils执行" + len + "次================");
        DestinationObject destination2 = new DestinationObject();
        for (int i = 0; i < len; i++) {
            PropertyUtils.copyProperties(destination2, origin);
        }

        stopwatch.stop();

        System.out.println("testApacheBeanUtilsPropertyUtils 耗时: " + stopwatch.elapsed(TimeUnit.MILLISECONDS));
    }

分别执行1000、10000、100000、1000000次耗时数(毫秒):

工具名称 执行1000次耗时 10000次 100000次 1000000次
Apache BeanUtils 390ms 854ms 1763ms 8408ms
Apache PropertyUtils 26ms 221ms 352ms 2663ms
spring BeanUtils 39ms 315ms 373ms 949ms
Cglib BeanCopier 64ms 144ms 171ms 309ms

性能对比结论

  1. Apache BeanUtils的性能最差,不建议使用。

  2. Apache PropertyUtils100000次以内性能还能接受,到百万级别性能就比较差了,可酌情考虑。

  3. spring BeanUtils和BeanCopier性能较好,如果对性能有特别要求,可使用BeanCopier,不然spring BeanUtils也是可取的。

  4. Cglib 的 BeanCopier 的拷贝速度是最快的,即使是百万次的拷贝也只需要 10 毫秒! 相比而言,最差的是 Commons 包的 BeanUtils.copyProperties 方法,100 次拷贝测试与表现最好的 Cglib 相差400 倍之多。百万次拷贝更是出现了2600 倍的性能差异!

    综上: 性能 setter > cglib > spring > apache PropertyUtils > apache BeanUtils

三、源码解析

此处只解析Apache BeanUtils 源码

解析源码

首先,抛出结论,Apache BeanUtils.copyProperties()性能差的原因是:

  1. 输出了大量的日志调试信息

  2. 重复的对象类型检查

开始分析

执行1000000次copy属性,然后通过jvisualvm查看方法耗时,如图:

img

发现最耗时的方法就是method.invoke(),但是spring的BeanUtils、PropertyUtils里也是采用反射来实现的,为什么效率相差这么大呢?img

看来Abstract.convert()和getIntrospectionData()占用了很大一部分时间.而且Apache BeanUtils中的日志输出也比较耗时。

与 Spring BeanUtils 进行对比

img

PropertyUtils和Apache BeanUtils核心代码区别在图中标注的地方。

Apache BeanUtils主要集中了各种丰富的功能(日志、转换、解析等等),导致性能变差。

而Spring BeanUtils则是直接通过反射来读取和写入,直抒胸臆,省去了其他繁杂的步骤,性能自然不差。

四、使用总结(重点)

对 Apache BeanUtils 的总结

  1. 拷贝性能 setter > Cglib BeanCopier > Spring BeanUtils > Apache PropertyUtils > Apache BeanUtils

  2. Apache BeanUtils.copyProperties()性能差的原因是对日志的大量操作和重复的对象类型检查

  3. Apache BeanUtils.copyProperties()坑点

    • 包装类默认值

    在进行属性拷贝时,低版本 BeanUtils 为了解决Date为空的问题会导致为目标对象的**原始类型的包装类属性赋予初始值**,如 Integer 属性默认赋值为 0,尽管你的来源对象该字段的值为 null。这个在我们的**包装类属性为 null 值时有特殊含义的场景**,非常容易踩坑!例如搜索条件对象,一般 null 值表示该字段不做限制,而 0 表示该字段的值必须为0。
    • 改用 Spring BeanUtils 时,要特别小心

    org.apache.commons.beanutils.BeanUtils.copyProperties(Object target, Object source);
    
    org.springframework.beans.BeanUtils.copyProperties(Object source, Object target);
    
    从方法签名上可以看出,这**两个工具类的名称相同,方法名也相同,甚至连参数个数、类型、名称都相同**。但是**参数的位置是相反的**。因此,如果你想更改的时候,千万要记得,将 target 和 source 两个参数也调换过来!
  4. Apache BeanUtils.copyProperties 有一种线程级别的“缓存”,第一次刷新缓存耗时较长,后续直接读”缓存”耗时较短,这种“缓存”是线程粒度

    • 单线程模型下,第一次访问BeanUtils.copyProperties耗时有200-300ms左右,后续访问几乎都是0ms,也就是微秒级别

    • 并发模型下,每个线程访问BeanUtils.copyProperties会有一次200-300ms耗时, 也就是高性能耗时次数与并发线程数一致

  5. Apache BeanUtils.copyProperties()可以在一定范围内进行类型转换(比如 int —> Integer),同时还要注意一些不能转换时候,会将默认null值转化成0;

  6. Apache BeanUtils 和 Apache PropertyUtils两个工具类都是对bean之间存在属性名相同的属性进行处理,无论是源bean或者是目标bean中多出来的属性均不处理

  7. Apache BeanUtils.copyProperties()可以在一定范围内进行类型转换(比如 int —> Integer),同时还要注意一些不能转换时候,会将默认null值转化成0;

  8. Property.copyProperties()则是严格的类型转化,必须类型和属性名完全一致才转化。PropertyUtils 支持为null的场景

对 Spring BeanUtils 的总结

  1. Spring BeanUtils 和 Apache BeanUtils 同名。从方法签名上可以看出,这两个工具类的名称相同,方法名也相同,甚至连参数个数、类型、名称都相同。但是参数的位置是相反的。因此,如果你想更改的时候,千万要记得,将 target 和 source 两个参数也调换过来!

  2. Spring BeanUtils.copryProperties()也可以在一定范围内进行类型转换(比如 int —> Integer)BeanUtils 对部分属性不支持 null,具体如下

    • java.util.Date 类型不支持,但是它的自雷java.sql.Date是被支持的。java.util.Date直接copy会报异常

    • Boolean,Integer,Long等不支持,会将null转化为0

    • String支持,转化后依然为null

  3. 如果存在属性完全相同的内部类,但是不是同一个内部类,即分别属于各自的内部类,则spring会认为属性不同,不会copy;

对 Cglib BeanCopier 的总结

  1. BeanCopier支持两种方式:

    • 一种是不使用Converter的方式,仅对两个bean间属性名和类型完全相同的变量进行拷贝;

    • 另一种则引入Converter,可以对某些特定属性值进行特殊操作

  2. Cglib 使用 BeanCopier.create()也是非常耗时,避免多次调用,尽可能做成全局初始化一次

  3. Cglib BeanCopier 的主要耗时方法就在 BeanCopier.create(),如果将该方法做成静态成员变量,则还可以大大缩小执行时间。BeanCopier是一种基于字节码的方式,其实就是通过字节码方式转换成性能最好的get、set方式,只需考虑创建BeanCopier的开销,如果我们将BeanCopier做成静态的,基本只需考虑get、set的开销,所以性能接近于get、set

  4. Cglib 的 BeanCopier 只拷贝名称和类型都相同的属性

    即使源类型是原始类型(int, shortchar等),目标类型是其包装类型(Integer, ShortCharacter等),或反之:都不会被拷贝

https://www.jianshu.com/p/2ca157963698)

拷贝严格性检验

* Apache BeanUtils.copyProperties()可以在一定范围内进行类型转换(比如 int ---> Integer),同时还要注意一些不能转换时候,会将默认null值转化成0;
* Property.copyProperties()则是严格的类型转化,必须类型和属性名完全一致才转化。PropertyUtils 支持为null的场景
* Spring BeanUtils.copryProperties()也可以在一定范围内进行类型转换(比如 int ---> Integer)BeanUtils 对部分属性不支持 null,具体如下
   a. java.util.Date 类型不支持,但是它的自雷java.sql.Date是被支持的。java.util.Date直接copy会报异常;
   b. Boolean,Integer,Long等不支持,会将null转化为0;
   c. String支持,转化后依然为null

参考链接:这四种对象属性拷贝方式,你都知道吗? – 风尘博客 – 博客园 (cnblogs.com)

BeanUtils.copyProperties(A,B)使用注意事项 – 龙-OSCAR – 博客园 (cnblogs.com)

BeanCopier、BeanUtils对象属性拷贝 – 简书 (jianshu.com)

Spring的BeanUtils的copyProperties方法需要注意的点 – 简书 (jianshu.com)

几种copyProperties工具类性能比较 – 简书 (jianshu.com)

Java Bean Copy框架性能对比-阿里云开发者社区 (aliyun.com)

为什么阿里要求避免使用 Apache BeanUtils 进行属性复制? – 简书 (jianshu.com)

0 89

1.权限申请系统

作用:该系统提供权限申请的入口

说明:如果需要使用到公司的某一系统(例如查看日志需要使用日志系统,进行研发需要使用研发系统,测试需要测试系统等),则在此申请对应系统的权限;

流程:确认开发项目(买鸭的某一子项目)—>检查有无相关系统的权限—>若无则在权限申请系统提交申请—>审批通过—>获得权限;

 

2.宙斯系统(STABLE)数据库操作相关

作用:该系统提供数据库相关操作的入口

两个环境:

1.测试环境+ stable环境(稳定环境)

2.预发布+灰度+线上

注:灰度发布(又名金丝雀发布)

是指在黑与白之间,能够平滑过渡的一种发布方式。 在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。

灰度发布中,常常按照用户设置路由权重,例如90%的用户维持使用老版本,10%的用户尝鲜新版本。不同版本应用共存,经常与A/B测试一起使用,用于测试选择多种方案。这里举一个灰度发布比较典型的例子

  我们希望上线一个PLMP系统的新的功能,需要修改dlp-personal服务,但是只有我们自己的用户limoumou验证通过后才能给所有用户使用。

  • 线上正在运行服务 dlp-personal-A,新部署一个服务 dlp-personal-B,使用新版本的Image和ConfigMap
  • 去负载均衡器页面修改转发规则,新增 header 转发规则将 token=liweiwu 的流量分配给 dlp-personal-B,剩余流量给 dlp-personal-A
  • 登录 liweiwu 用户进行相关测试,发现问题进行修改并更新 dlp-personal-B 的Image和ConfigMap,直至验证通过
  • 修改 dlp-personal-A 的Image和ConfigMap与 dlp-personal-B 一致
  • 删除负载均衡器上的转发规则,所有流量指向服务 dlp-personal-A
  • 删除服务 dlp-personal-B

简单总结:灰度测试即为在维持原版本的基础上,提供新版本给部分用户,类似于内测,根据用户反馈调整和完善新版本,最终平滑上线新版本

参考链接:灰度环境搭建笔记_定格空间-CSDN博客_灰度环境

如何理解灰度发布_xialingming的博客-CSDN博客_如何灰度发布

什么是灰度测试 ? – 知乎 (zhihu.com)

灰度发布:灰度很简单,发布很复杂_程序人生的博客-CSDN博客_灰度发布

 

环境说明:

预发布+灰度+线上

 

测试 + stable 

 

如果MySQL或者Redis没有权限,可以去工作流平台申请

 

3.乐效研发管理系统

1.我的工作台— 可创建个人的待办事项(比如以后的任务),可查看个人贡献,过程质量

2.可以对应用进行管理,可以查看,切换相应的项目机器状态

3.乐效发布系统(版本发布管理)

分为四个阶段:

  • 版本规划
  • 版本质检
  • 版本发布
  • 发布观察

简单描述:新建版本后,需要进行版本规划,而版本规划的第一步为 将项目工程与需求相关联,比如 “ 添加用户预定操作”,

0 90

今天和猪仔讨论项目的时候遇到一个需求

目前有一个实体类,里面有一些String,int的属性,还有一个List<>类型的属性

但是这个List<>括号里面内容不同而形成不同实体

比如记录日志,该日志分为工作日志和系统日志,那么这两种不同类型的日志的日志时间,日志数量这两个属性对应String、int,是一样的,不同之处在于List<>传入的参数类型不同

一个传入List<Work> 一个传入List<Sys>

我们不想写两个不同的实体类,想用一个实体类来表示这两种不同的日志对象

一开始我的思路是写成 List<Object> 传入,再进行转型

可惜这样无法得到想要的结果,因为List<Object>应该看成一个整体,他不是List<Work>,也不是List<Sys>的父类

因此无法实现向上转型

后来改用 ? 泛型 即可 详情见下文

https://blog.csdn.net/eeeeasy/article/details/80999650?utm_source=app&app_version=4.14.1?utm_source=app

0 99

disinct 关键字

 

至少SQL Server,HQL,Oracle都有效

通常SQL中对表中数据去重,会首先想到 distinct 关键字,

能实现的需求

1. distinct可以对单个字段去重
select distinct name from A


2. 对多个字段去重时,此时所列的字段需要同时满足才会起到去重效果,否则不会去重

select distinct name, id from A       --name和id同时重复才能去重,有一个不一样都不会去重

        --上面的写法是对name和id列都去重,而不是仅对name去重,id不去重

select distinct * from A      --*所代表的所有字段都重复时,才能去重

不能实现的需求

3. 不能实现指定字段去重,其他字段不去重的效果
select name, distinct id from A   --仅希望对id列进行去重,name列不去重,xxx这样是不行的,无法实现 ,而且会提示错误,因为distinct必须放在开头

问题:
如果想实现针对某一字段去重,其他字段是否重复不关心的效果怎么处理?
答:
可以采用row_number()的窗口函数
转换问题,可以转换为对重复的行取top1,这样使用over()函数,对指定列分组,排序,然后结合row_number()给每一组的数据一列序列,再对集合取序列为1的行

结合一些用例更容易懂一些。

0 79

首先,我们需要明确数据一致性的概念

 

数据一致性,就是当多个用户试图同时访问一个数据库,它们的事务同时使用相同的数据,可能会发生以下四种情况:丢失更新、未确定的相关性、不一致的分析和幻想读。

脏读、丢失修改、不可重复读、幻读 (并发事务下引起的问题)

搜集了一些资料,我发现,关于一致性这个概念,众说纷纭,而且许多答案将应用层的一致性与数据库层面的一致性混淆,由于本人才疏学浅,只能摘抄其他优秀答案:

如何理解数据库事务中的一致性的概念?

请看下面Wikipedia中关于数据库事务一致性的定义

Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades,triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct.

我对这段话的理解:

  • 数据库事务的一致性是指保证事务只能把数据库从一个有效(正确)的状态“转移”到另一个有效(正确)的状态。那么,什么是数据库的有效(正确)的状态?满足给这个数据库pred-defined的一些规则的状态都是 valid 的。这些规则有哪些呢,比如说constraints, cascades,triggers 及它们的组合等。具体到某个表的某个字段,比如你在定义表的时候,给这个字段的类型是number类型,并且它的值不能小于0,那么你在某个 transaction 中给这个字段插入(更改)为一个 String 值或者是负值是不可以的,这不是一个“合法”的transaction,也就是说它不满足我们给数据库定义的一些规则(约束条件)。
  • “This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct. ” 这又怎么理解呢?在数据库的角度来看,它只关心 transaction 符不符合定义好的规则,符合的就是legal的,不符合的就是illegal的。transaction 是否正确是从应用层的角度来看的,数据库并不知道你应用层的逻辑意义,它不保证应用层的transaction的正确性,这个逻辑正确性是由应用层的programmer来保证的。 这么说估计还是抽象,那么看下面我们熟知的转账的例子。
Table: Account
Columns:   Name(string), Balance(int)
约束条件:无

执行下面一个事务(A,B的初始余额均为1000,A给B转账1200)

1.  往表Account插入数据(A,1000)
2. 往表Account插入数据 (B,1000)
3. A给B转账1200,更新A的余额为-200,(A,-200)
4. B的余额增加1200,更新B的余额为2200(B,2200)

那么,数据库会认为这个 transaction 合不合法呢?也就是它满不满足我们给数据库的定义的规则呢?答案就是这个 transaction 是合法的,因为你定义表的时候没有约定 Balance 不能小于0。虽然我们从应用层的角度来看,这个transaction是不正确的,因为它不符合逻辑- balance不能小于0. 但我们数据库只关心你的 transaction 满不满足你的数据库定义的rule,不关心它具有什么业务的逻辑,这个业务逻辑是应该由应用层来理解并处理的。修改一下上面这个例子

Table: Account
Columns:   Name(string), Balance(int)
约束条件:Balance >= 0

执行下面一个事务(A,B的初始余额均为1000,A给B转账1200)

1.  往表Account插入数据(A,1000)
2. 往表Account插入数据 (B,1000)
3. A给B转账1200,更新A的余额为-200,(A,-200)
4. B的余额增加1200,更新B的余额为2200(B,2200)

注意,这里增加了约束条件Balance > 0, 上面的这个transaction违反了规则Balance>=0,那么这个事务数据库认为它是非法的,不满足一致性的要求,所以数据库执行这个事务会失败

最后请再认真研读一下链接 Consistency (database systems) 中的这段话。

This(Consistency)does not guarantee correctness of the transaction in all ways the application programmer might have wanted (that is the responsibility of application-level code) but merely that any programming errors cannot result in the violation of any defined database constraints.[1]

 

今天在进行开发的时候,发现Controller 里面 的方法参数 可以用 @ModelAttribute 修饰

之前用过 @RequestBody、@RequetParam、@PathVariable 注解修饰 参数@ModelAttribute 还是头一次见到,特此补充

 

@ModelAttribute的用法大概有两种:

一种是直接标记在方法上,

一种是标记在方法的参数中,

两种标记的方法产生效果也各不相同

一、直接标记在方法上

注:用@ModelAttribute 修饰的方法会先于请求的方法执行!!!

@Controller
@RequestMapping(value=”model”)
public class ModelAttributeTest {

@ModelAttribute
public void init()
{
System.out.println(“最先执行的方法”);
}

@ModelAttribute
public void init02()
{
System.out.println(“最先执行的方法02”);
}

@RequestMapping(value=”modelTest.do”)
public String modelTest()
{
System.out.println(“然后执行的方法”);
return “modelTest”;
}

@ModelAttribute
public void init03()
{
System.out.println(“最先执行的方法03”);
}
}

 

部署后运行,点击页面测试按钮,查看控制台输出,这个时候你会发现,后台控制器并没有直接进入modelTest.do的路径,而是先执行了被@ModelAttribute标记的init方法。应该这么理解,当同一个controller中有任意一个方法被@ModelAttribute注解标记,页面请求只要进入这个控制器,不管请求那个方法,均会先执行被@ModelAttribute标记的方法,所以我们可以用@ModelAttribute注解的方法做一些初始化操作。当同一个controller中有多个方法被@ModelAttribute注解标记,所有被@ModelAttribute标记的方法均会被执行,按先后顺序执行,然后再进入请求的方法。

 

当@RequestMapping标记和@ModelAttribute同时标记在一个方法上

点击测试页面,会发现当两个注解同时注解到一个方法上时,方法的返回值会变成model模型的返回值,key是标记的名

 

 

二.@ModelAttribute标记在参数前

 

@Controller
@RequestMapping(value=”model”)
public class ModelAttributeTest {

@RequestMapping(value=”modelTest.do”)
public String modelTest(@ModelAttribute(“pojo”) PojoTest pojo)
{
try {
pojo.setUserName(new String(pojo.getUserName().getBytes(“iso-8859-1″),”utf-8”));
pojo.setSex(new String(pojo.getSex().getBytes(“iso-8859-1″),”utf-8”));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(pojo);
return “modelTest”;
}

}

点击页面测试,页面文本框会显示URL地址传递过来的参数,因为SpringMVC会自动匹匹配页面传递过来的参数的name属性和后台控制器中的方法中的参数名,如果参数名相同,会自动匹配,如果控制器中方法是封装的bean,会自动匹配bean中的属性,其实这种取值方式不需要用@ModelAttribute注解,只要满足匹配要求,也能拿得到值

@RequestBody

作用:

i) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上;

ii) 再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。

使用时机:

A) GET、POST方式提时, 根据request header Content-Type的值来判断:

  •     application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理);
  •     multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据);
  •     其他格式, 必须(其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理);

B) PUT方式提交时, 根据request header Content-Type的值来判断:

  •     application/x-www-form-urlencoded, 必须;
  •     multipart/form-data, 不能处理;
  •     其他格式, 必须;

说明:request的body部分的数据编码格式由header部分的Content-Type指定;

今天在看 《阿里巴巴开发规范》的OOP规约时,发现一个对于我来说很陌生的浮点数类型

BigDecimal

这个数据类型我见过,但无法准确的描述和理解其具体含义,因此趁今天碰到了,特此记录

通过百度百科发现,它的作用是巨大的,当浮点数计算追求完美精度的时候(金融),不可以使用Double和Float

那么 我们一起来看看,为什么要用BigDecimal?怎么用BigDecimal吧?

首先,我们来猜测一下下列输出结果

在未了解BigDecimal类之前,我会觉得这些输出跟我们现实中得到的值一样,可是结果却是

那为什么会出现这种情况呢?

因为不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。

根本原因是:十进制值通常没有完全相同的二进制表示形式;十进制数的二进制表示形式可能不精确。只能无限接近于那个值。

但是,在项目中,我们不可能让这种情况出现,特别是金融项目,因为涉及金额的计算都必须十分精确,你想想,如果你的支付宝账户余额显示193.99999999999998,那是一种怎么样的体验?

【BigDecimal是什么?】

1、简介
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

2、构造器描述
BigDecimal(int) 创建一个具有参数所指定整数值的对象。
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 //不推荐使用
BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。//推荐使用

3、方法描述
add(BigDecimal) BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。
multiply(BigDecimal) BigDecimal对象中的值相乘,然后返回这个对象。
divide(BigDecimal) BigDecimal对象中的值相除,然后返回这个对象。
toString() 将BigDecimal对象的数值转换成字符串。
doubleValue() 将BigDecimal对象中的值以双精度数返回。
floatValue() 将BigDecimal对象中的值以单精度数返回。
longValue() 将BigDecimal对象中的值以长整数返回。
intValue() 将BigDecimal对象中的值以整数返回。

特别说明一下,为什么BigDecimal(double) 不推荐使用

看上面代码运行结果,你就应该知道为什么不推荐使用了,因为用这种方式也会导致计算有问题

为什么会出现这种情况呢?

JDK的描述:1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法

当double必须用作BigDecimal的源时,请使用Double.toString(double)转成String,然后使用String构造方法,或使用BigDecimal的静态方法valueOf,如下

【Decimal怎么用?】

直接上代码,都挺简单的,最基本的加减乘除,应该能看的懂

这边特别提一下,如果进行除法运算的时候,结果不能整除,有余数,这个时候会报java.lang.ArithmeticException:

这边我们要避免这个错误产生,在进行除法运算的时候,针对可能出现的小数产生的计算,必须要多传两个参数

divide(BigDecimal,保留小数点后几位小数,舍入模式)

原文链接:(转载并整理自)https://blog.csdn.net/qq_35868412/article/details/89029288