分类 Java 下的文章

Git实践

分支

git branch 查看所有分支
git branch v4.4 创建v4.4分支
git checkout v4.4 切换到v4.4分支

主干合并分支代码
git checkout master
git merge branch_name
主干合并分支某个commit代码
git checkout master
git cherry-pick commit_code

分支合并主干代码
git checkout branch_name
git merge master

获取远程分支
git checkout -b v2.0 origin/2.0

删除分支
git branch -d branch_name
删除远程分支
git push origin --delete branch_name
本地分支关联远程分支:
git branch --set-upstream-to=origin/newconfigcenter newconfigcenter

提交

git commit
git commit -m "提交注释"
重新提交
git commit --amend
增补提交
git commit -C head -a --amend

撤销回退

撤销commit
git reset --hard <commit_id>
撤销文件add
git reset HEAD <file>

reset命令有3种方式:
1:git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和index信息
2:git reset –soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可
3:git reset –hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容

跟踪

取消对已纳入git版本管理的文件跟踪
注:该命令只能取消提交到暂存区之前的文件,可以先用git reset <文件名>将暂存区的文件回退到暂存区之前,然后再取消跟踪。或者使用git checkout恢复到git最近一个版本。
git update-index --assume-unchanged <取消跟踪的文件>
git update-index --no-assume-unchanged <撤销取消跟踪的文件>

FAQ

【FAQ】git merge 后 push 到 Gerrit 失败,提示 no new changes ?
需求:Git 分支合并
问题:使用 git merge 在本地执行分支合并操作,然后想 push 到 gerrit 上评审入库,可是在提交时,提示:
! [remote rejected] HEAD -> refs/for/dev (no new changes)
分析:no new changes 的意思,是说,这个合并,是个线性的合并。而合并的那些历史的 commit 节点,在 gerrit 上都已经评审过了,都是已有的 change 单,所以 gerrit 认为没有新的提交,就不让你提交评审。
方法1: 在 git merge 的时候,加上 --no-ff 参数,是为了让它生成一个新的 commit,这样就可以提交了~(不过生成的 gerrit change 是看不到改动信息的):git merge --no-ff v4.5  或者 git merge branch_name -m "分支合并"
方法2:不经过 gerrit,直接 push 入远程库。(不推荐)

【FAQ】$ gitdir=$(git rev-parse --git-dir); scp -p -P 12345 longchaoqun@smartgit:hooks/commit-msg ${gitdir}/hooks/    返回结果:Either -f or -t option should be set
解决:直接从服务器上下载http://smartgit/gerrit/tools/hooks/commit-msg, 把commit-msg文件放到.git/hooks目录吧

【FAQ】git pull之后,merge了最新一个commit版本,但是没有changeId。
解决:重新提交后再push。git commit --amend  ; git push

下载老版本JDK

JDK的不同版本,很容易导致工程编译和启动报错,特别是项目里面没有统一使用某一类的第三方包,版本冲突等导致工程启动时各种异常。

Java有时候竟变成了一处编译,到处调试的情况,所以最好是组里面所有人统一使用同一个版本的JDK和第三方包。有了maven之后好了很多,但如果管理不善还是会出现这样的问题的。

JDK的老版本如1.6 1.7的下载地址很难找,今天给组里面的人搭环境的时候,也遇到了这种问题,没有找他们要安装包是因为他们都是linux环境,现需要搭建一个新的windows开发环境。

下面是JDK 老版本的下载方式:

  1. 根据jdk版本,在google搜索关键字。如:jdk 1.7.0_65,搜索 JDK 7u65
  2. 点击 Java Archive Downloads 链接(一般是第一条结果),在新页面选择对应的版本 Java SE Development Kit 然后下载
  3. 注意下载版本:linux | windows、i586(32位) | x64(64位) | ia64(inter的一种,与x86-64不兼容)

刚发现百度经验上有一篇介绍,也很方便。地址:如何在官网下载java JDK的历史版本

MyBatis之批量增删

mybatis-logo.png

1.创建表和序列

create table USER_INFO
(
id NUMBER(12),
user_name VARCHAR2(40),
password VARCHAR2(40),
status NUMBER(1),
sex NUMBER(1),
age NUMBER(3),
birth_date DATE,
create_date DATE,
version_date DATE
);

create sequence SEQ_USER_INFO
minvalue 1
maxvalue 99999999999999999999
start with 1
increment by 1
cache 20;
数据库表一般都要有数据的生成时间以及修改时间,用于记录数据变动情况。数据是否有效可以通过一个表状态字段来判断,或者加一个数据有效时间的字段。

为什么要说这点,因为现在就遇到了这样的情况,公司的几个后台管理系统,数据模型里都没有这些字段。有时候程序出了问题,熟悉业务流程和逻辑的话只需要看看数据库里面的数据就可以定位问题在哪里,但是如果没有时间参数,有时候就变得有点困难。

2.增、删、改
parameterType可以是HashMap,也可以是自定义的Java Bean。
增:

<select id="insert" parameterType="java.util.HashMap">

<selectKey keyProperty="id" resultType="java.lang.Integer" order="BEFORE">
    select seq_user_info.nextval from dual
</selectKey>
insert into user_info(id,user_name,password,status,sex,age,birth_date,create_date,version_date)
values(#{id},#{userName},#{password},#{status},#{sex},#{age},#{birthDate},sysdate,sysdate)

</select>
删:

<delete id="delete" parameterType="java.util.HashMap">

delete from user_info where id = #{id}

</delete>
改:

<update id="update" parameterType="java.util.HashMap">

update user_info
   set status = #{status},
    sex = #{sex},
    age = #{age},
    birth_date = #{birthDate},
    version_date = sysdate
 where id = #{id} 

</update>

<update id="updatePassword" parameterType="java.util.HashMap">

update user_info 
   set password = #{password},
       version_date = sysdate
 where id = #{id}

</update>
3.批量增、删
批量新增数据,mysql和oracle因为语法支持不一样,所以略有不同。
mysql:

<insert id="insertBatch" parameterType="java.util.HashMap">

<selectKey keyProperty="id" resultType="java.lang.Integer" order="BEFORE">
    select seq_user_info.nextval from dual
</selectKey>
insert into user_info(id,user_name,password,status,sex,age,birth_date,create_date,version_date)
values
<foreach collection="userList" item="item" index="index" separator=",">
     (#{item.id} id,
      #{item.userName} user_name,
       #{item.password} password,
       #{item.status} status,
       #{item.sex} sex,
       #{item.age} age,
       #{item.birthDate} birth_date,
       #{item.createDate} create_date,
       #{item.versionDate} version_date)
</foreach> 

</insert>
oracle:
oracle不支持values后批量插入,需修改为select + union

<insert id="insertBatch" parameterType="java.util.HashMap">

insert into user_info(id,user_name,password,status,sex,age,birth_date,create_date,version_date)
select seq_user_info.nextval id,t.* from (
<foreach collection="userList" item="item" index="index" separator="union all">
    select #{item.userName} user_name,
            #{item.password} password,
            #{item.status} status,
            #{item.sex} sex,
            #{item.age} age,
            #{item.birthDate} birth_date,
            #{item.createDate} create_date,
            #{item.versionDate} version_date
    from dual
</foreach> 
) t

</insert>
删:
idList是map里面的key值,用map方便以后加参数,比如只能删除不可用的用户

<delete id="deleteBatch" parameterType="java.util.HashMap">

delete from user_info where id in
<foreach item="id" index="index" collection="idList" open="(" separator="," close=")">
    #{id}
</foreach>

</delete>

开发人员自助SQL查询

应用背景:
很多公司项目上线以后,线上环境是由专门的人员去维护的,包括服务器和数据库的配置、操作,开发人员是没有权限登录数据库的。

一个是防止开发人员误操作导致异常情况出现,影响系统运行,因为开发人员多了水平可能参差不齐。
二是线上环境的数据涉及到公司机密,不可泄漏,像电商公司的用户、订单、商品等数据就属于机密,供应链系统的订单、物流、发票数据等等。不给开发人员权限,防止因为人员流动导致公司遭受损失。

问题描述:
但很多时候出现了问题,我们需要查看线上环境的数据来定位问题出现在哪里,这个时候往往是程序代码已经排查没有发现问题,本机环境、镜像环境也都运行正常。
比如A系统定时从B系统获取一部分数据过来,用spring定时器定时执行存过,拉取数据,但上线时候总发现一些数据没有同步过来,查找代码又没有发现问题。这时候如果知道哪部分数据,哪个时间段的数据没有同步过来,就好定位问题了。

解决办法:
出现这种情况一般是申请数据库权限查看线上数据情况,当然还有一种办法,·就是系统前台功能提供一个查询功能的界面,可以让开发人员执行sql查看数据。·其实这个现在已经很普遍了,像支持wordpress程序的后台服务器管理系统,就提供了wordpress相关表的数据查询等操作。

这个功能简单一点就是一个页面,里面有一个查询文本框和查询按钮,然后是一个列表用于展示数据。通过判断sql中前面6个字符是insert还是update、delete、select来调用对应的方法来执行SQL语句。


`当然提供这个功能也是有一定风险的,权限控制很重要。
哪些人哪些帐号可以使用这个功能,是可以增删改查,还是只能简单查询,都需要仔细评估。`

字符串转换

`问题:将字符串中的'aa'转换成'bb',但是如果是'a'、'aaa'等,则不进行转换。
如:
aap -> bbp
apa -> apa
aaapaapa -> aaapbbpa`
网上看见一题,用硬代码是这样:将字符串转换成字符数组处理,创建一个相同长度的数组来标记当前字符是否转换,每次处理一个或一段字符。

如果用正则表达式呢?

    public void transferString(String str) {
//0.条件初始化
char[] cArr = str.toCharArray();    //将字符串转为字符数组处理
char[] flagArr = new char[cArr.length];    //字符数组标记,判断是否转换
for(int i = 0; i < flagArr.length; i++) { //字符数组初始为0
    flagArr[i] = '0';
}

//1.循环遍历数组,每次处理一个或者一段字符串。
for(int i = 0; i < cArr.length - 1; ) {   
    //默认处理两个,如果不满足'aa',则处理第一个,如果满足,则处理下面的一段字符    
    int len = 2;
    boolean flag = true; //判断处理标识,为true表示转为b,为false表示不转
    char a = cArr[i];
    char b = cArr[i+1];
    //判断处理
    if(a == 'a' && b == 'a') {
        //第一个和第二个都是a,判断后面的
        int j = i + 2;
        while(true) {
            if(j == cArr.length) { //如果已经是最后两个字符,不再处理
                break;
            }
            if(cArr[j] == 'a') {//如果后面的也是'a',则不进行转换
                len += 1;
                j++;
                flag = false; //修改标记
            } else {
                break;
            }
        }
        //处理数组标记
        for(int k = i; k < i + len; k++) {
            if(flag) {
                flagArr[k] = '1';
            } else {
                flagArr[k] = '0';
            }
        }
        i += len; //修改下标,处理下一段字符串
    } else {
        flagArr[i] = '0';
        flag = false;
        i++;
        //如果是倒数第2元素
        if(i == (cArr.length - 1)) {
            if(flag) {
                flagArr[flagArr.length - 1] = '1';
            }
        }
    }
    
}

//对标记数组遍历,转换字符
for(int m = 0; m < flagArr.length; m++){
    if(flagArr[m] == '1') {
        cArr[m] = 'b';
    }
}

//打印,查看结果
System.out.println(str);
for(char c : cArr) {
    System.out.print(c);
}
}

Java虚拟机运行时数据区

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

java_runtime_dataarea.png
图1 Java虚拟机运行时数据区

程序计数器:

程序计数器可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都要有一个独立的程序计数器。

如果线程执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Underfined)。

特点:线程私有,较小的内存空间。
异常状况:无 (Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域)。

Java虚拟机栈:

虚拟机执行Java方法时的内存模型:每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表:存放了编译期可知的各种基本的数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(指向对象起始地址的引用指针,或者指向代表对象的句柄或其他与此对象相关的位置)、returnAddress类型(指向一条字节码指令地址)。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要的在桢中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

特点:线程私有,生命周期与线程相同。
异常状况:

  • 线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
  • 虚拟机动态扩展时,如果无法申请到足够的内存,抛出OutOfMemoryError异常

本地方法栈:

和虚拟机栈类似,区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机执行Native方法服务。有的虚拟机将两者合二为一,如Sun HotSpot虚拟机。

Java堆:

在虚拟机启动时创建,存放对象实例的内存区域。

特点:

  • 所有线程共享
  • Java虚拟机管理的内存中最大的一块
  • 垃圾收集器管理的主要区域
  • 可以处于物理上不连续的内存空间中(逻辑上一致)
  • 堆容量可以动态扩展。

异常状况:如果堆中没有内存空间完成实例分配,并且堆也无法再扩展时,抛出OutOfMemoryError异常

方法区:

和Java堆类似,用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。

特点:线程共享,垃圾回收行为较少出现。
异常状况:当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

运行时常量池:

是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分信息在类加载后进入方法区的运行时常量池存放。

特点:具有动态性,运行期间也可以将新的常量放入常量池中。
异常状况:当常量池无法满足内存分配需求时,抛出OutOfMemoryError异常。

直接内存:

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。

JDK1.4加的NIO中,ByteBuffer有个方法是allocateDirect(int capacity) ,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

异常状况:服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

Java缓存这点事

  1. 什么是缓存
    缓存,它介于应用程序和永久性数据存储源(如硬盘文件、数据库等)之间,其作用是降低应用程序读写永久性数据存储源的频率,从而提高应用的运行性能。

缓存中的数据是数据存储源中的拷贝,应用程序在运行时直接读写缓存中的数据,在某些特定的时刻按照缓存中的数据来同步更新数据存储源。

  1. 为什么要用缓存,哪些场景会用到缓存
    缓存中的介质通常是内存,永久性数据存储源一般是硬盘、磁盘或者数据库,应用程序读写内存的速度显然比读写磁盘快,访问数据库也是比较耗资源的,速度也比较慢的。正确有效的使用缓存能大大提高应用程序的性能。

常用的缓存场景:

  • 内存缓存,如类中的static Map静态变量
  • 应用程序,如页面缓存,动态页面静态化、Servlet缓存等,如Hibernate框架的对象缓存
  • session、request等,如session中保存登录用户的认证权限。
  • 浏览器缓存,如cookie
  • 数据库缓存,如Query Cache、Data Buffer
  1. Java缓存的类型,以及它们的特点和用法
    Java缓存有两种:

一、内存缓存,如一个类中的static Map静态变量,优点是使用简单,缺点是对象的有效性和生命周期无法控制,容易导致内存使用的急剧上升。

生命周期的控制可以通过SoftReference、WeakReference、PhantomReference这三个对象来执行,即软引用,弱引用,虚引用。

参考:深入探讨 java.lang.ref 包

二、文件缓存,是指把数据存储到磁盘上,如XML格式文件,也可以序列化文件DAT文件格式或者其他。

内存级别的缓存很多情况下是满足不了企业级应用的,大数据量的缓存肯定还是要保存到文件,这个时候光用JDK就比较复杂了, 需要用到第三方开源框架来实现了。

  1. Java缓存框架
    常用的Java缓存框架有Oscache、Ehcache、Jcache、Jbosscache等,推荐使用Ehcache和Oscache,Hibernate采用了Ehcache作为其缓存机制默认实现,Oscache是iBatis推荐的缓存框架。

常用框架简介:

OSCache是个一个广泛采用的高性能的J2EE缓存框架,OSCache能用于任何Java应用程序的普通的缓存解决方案。 OSCache有以下特点:缓存任何对象,你可以不受限制的缓存部分jsp页面或HTTP请求,任何java对象都可以缓存。拥有全面的API--OSCache API给你全面的程序来控制所有的OSCache特性。永久缓存--缓存能随意的写入硬盘,因此允许昂贵的创建(expensive-to-create)数据来保持缓存,甚至能让应用重启。支持集群--集群缓存数据能被单个的进行参数配置,不需要修改代码。缓存记录的过期--你可以有最大限度的控制缓存对象的过期,包括可插入式的刷新策略(如果默认性能不需要时)。

EHCache 是一个纯java的在进程中的缓存,它具有以下特性:快速,简单,为Hibernate2.1充当可插入的缓存,最小的依赖性,全面的文档和测试。

Ehcache详细解读Ehcache 整合Spring 使用页面、对象缓存

JCache是个开源程序,正在努力成为JSR-107开源规范,JSR-107规范已经很多年没改变了。这个版本仍然是构建在最初的功能定义上。官方网站 http://jcache.sourceforge.net/

JBossCache是一个复制的事务处理缓存,它允许你缓存企业级应用数据来更好的改善性能。缓存数据被自动复制,让你轻松进行JBoss服务器之间的集群工作。JBossCache能够通过JBoss应用服务或其他J2EE容器来运行一个MBean服务,当然,它也能独立运行。 JBossCache包括两个模块:TreeCache和TreeCacheAOP。 TreeCache --是一个树形结构复制的事务处理缓存。 TreeCacheAOP --是一个“面向对象”缓存,它使用AOP来动态管理POJO(Plain Old Java Objects) 注:AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。

Java Caching system JSC(Java Caching system)是一个用分布式的缓存系统,是基于服务器的java应用程序。它是通过提供管理各种动态缓存数据来加速动态web应用。 JCS和其他缓存系统一样,也是一个用于高速读取,低速写入的应用程序。动态内容和报表系统能够获得更好的性能。如果一个网站,有重复的网站结构,使用间歇性更新方式的数据库(而不是连续不断的更新数据库),被重复搜索出相同结果的,就能够通过执行缓存方式改进其性能和伸缩性。

ShiftOne Java Object Cache是一个执行一系列严格的对象缓存策略的Java lib,就像一个轻量级的配置缓存工作状态的框架。官方网站 http://jocache.sourceforge.net/

SwarmCache是一个简单且有效的分布式缓存,它使用IP multicast与同一个局域网的其他主机进行通讯,是特别为集群和数据驱动web应用程序而设计的。SwarmCache能够让典型的读操作大大超过写操作的这类应用提供更好的性能支持。 SwarmCache使用JavaGroups来管理从属关系和分布式缓存的通讯。官方网站 http://swarmcache.sourceforge.net

Whirlycache是一个快速的、可配置的、存在于内存中的对象的缓存。它能够通过缓存对象来加快网站或应用程序的速度,否则就必须通过查询数据库或其他代价较高的处理程序来建立。

  1. Hibernate的两级缓存
    一级缓存是session级别的缓存,属于进程范围内的缓存,由Hibernate自行管理一般无需进行干预。

二级缓存是SessionFactory级别的缓存,属于集群范围与进程范围的缓存,它可以进行配置和更改,可以动态加载和卸载。

Hibernate两级缓存详细解析,请参考:

对Hibernate一级缓存与二级缓存的解析
Hibernate3缓存总结
hibernate二级缓存攻略

  1. 缓存存在的几个问题
    在Web应用中,正确使用缓存能大大提供程序的运行效率,但是使用中有这么几个问题还需注意:

缓存数据的有效时间控制
缓存数据的同步更新
缓存数据的多线程并发控制

  1. 缓存的几种误区
    有这么几种常见的误区:
  • 太过于依赖默认的序列化机制
  • 缓存大对象
  • 使用缓存机制在线程间进行数据的共享
  • 认为调用缓存API之后,数据会被立刻缓存起来
  • 缓存大量的数据集合,而读取其中一部分
  • 缓存大量具有图结构的对象导致内存浪费
  • 缓存应用程序的配置信息
  • 使用很多不同的键指向相同的缓存项
  • 没有及时的更新或者删除再缓存中已经过期或者失效的数据