java开发日经验常总结

try{}catch(){} 使用

try{}catch(){}资源释放

Java8里的一个新语法特性:try-with-resources。
try-with-resources的特性就是,在try( …)里声明的资源,会在try-catch代码块结束后自动关闭掉。

try(OutputStream out = new FileOutputStream(filepath);) {
            global_out = out;
            out.write((filepath+"inside try catch block").getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }

try{}catch(){}与@transactional 同时使用

业务需求:需要捕获到@Transactional中的异常,并不影响事物回滚

问题描述:
@Override
@Transactional()
public void add() {  
     try {  
        insert1();  
        insert2();  
     } catch (Exception e) {  
          e.printStackTrace(); 
          //  /如果insert2()抛了异常,insert1()不会回滚
     }  
解决方式1
@Override
@Transactional()
public void add() {  
     try {  
        insert1();  
        insert2();  
     } catch (Exception e) {  
          e.printStackTrace(); 
         // 手动硬编码开启spring事务管理
         TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
     }  

该方法执行后我们可以看到事务最后执行了,但实际上 事务 执行只是因为手动硬编码开启spring事务管理起了作用 而方法上的注解并没有起作用

解决方式2
@Override
@Transactional()
public void add() {  
     try {  
        insert1();  
        insert2();  
     } catch (Exception e) {  
          e.printStackTrace(); 
         // 抛出runtime异常
           throw new RuntimeException();
     }  

上述方法执行后我们可以看到事务是执行了的,但这里有个小细节:@Transactional不做任何配置 默认只是对抛出的unchecked异常回滚,checked异常不会回滚,为了让所有异常都会让事务启动可以将 @Transactional配置为 @Transactional(rollbackFor = Exception.class)

除了RuntimeException以外的,都是checked Exception

java开发日经验常总结_第1张图片spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(spring默认取决于是否抛出runtime异常).
如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足

解决方式2.1
@Override
@Transactional(rollbackFor=Exception.class)
public void add() {  
     try {  
        insert1(); 
        insert2();  
     } catch (Exception e) {  
          e.printStackTrace(); 
          //抛出Exception,初catch住如果想让事务回滚必段再往外拋
         throw new Exception ();//
     }  

上述方法对于checked与unchecked异常,事务都可以回滚
1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

2 让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

3 不需要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

注意: 如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。

spring事务@Transactional注解在同一个类中的方法之间调用不生效的原因及解决方案

例如下情况,在方法baz中调用bar方法,bar方法事务无效

public class Foo {
    @Transactional
    public void bar() { /* … */ }

    public void baz() {
        this.bar();
    }
}

原因

Transactional是Spring提供的事务管理注解。

重点在于,Spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。

而在同一个class中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象。所以Spring无法切到这次调用,也就无法通过注解保证事务性了。

也就是说,在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用。
也就是说只有通过代理对象来调用事务方法,事务才会起作用

解决方案

解决方法1:

将事务方法放到另一个类中(或者单独开启一层,取名“事务层”)进行调用,即符合了在对象之间调用的条件。即单独新建一个类来处理

解决方法2:

获取本对象的代理对象,再进行调用。具体操作如:

  1. Spring-content.xml上下文中,增加配置:

  2. 在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。

解决方法3:

很多时候,方法内调用又希望激活事务,是由于同一个方法既有DAO操作又有I/O等耗时操作,不想让耗时的I/O造成事务的太长耗时(比如新增商品同时需要写入库存)。此时,可以将I/O做成异步操作(如加入线程池),而加入线程池的操作即便加入事务也不会导致事务太长,问题可以迎刃而解。
解决方法4:

用@Autowired 注入自己 然后在用注入的bean调用自己的方法也可以

public class Foo {
@Autowired 
Foo  foo//注入自已
    @Transactional
    public void bar() { /* … */ }

    public void baz() {
        this.foo.bar();
    }
}

springboot参数校验

普通实体类校验

实体类

@Data
public class UserInfo {
    @NotNull(message = "username cannot be null")
    private String name;

    @NotNull(message = "sex cannot be null")
    private String sex;

    @Max(value = 99L)
    private Integer age;
}

然后在controller方法中用@RequestBody表示这个参数接收的类:

@RestController
public class PingController {
   

    @GetMapping("metrics/ping")
    public Response<String> ping() {
        return new Response<>(ResponseCode.SUCCESS, null,"pang");
    }

    @PostMapping("/getUser")
    public String getUserStr(@RequestBody @Validated UserInfo user, BindingResult bindingResult) {
        validData(bindingResult);

        return "name: " + user.getName() + ", age:" + user.getAge();
    }

    private void validData(BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            StringBuffer sb = new StringBuffer();
            for (ObjectError error : bindingResult.getAllErrors()) {
                sb.append(error.getDefaultMessage());
            }
            throw new ValidationException(sb.toString());
        }
    }
}

######实体类分组校验

在实际开发中经常会遇到这种情况:想要用一个实体类去接收多个controller的参数,但是不同controller所需要的参数又有些许不同,而你又不想为这点不同去建个新的类接收参数。比如有一个/setUser接口不需要id参数,而/getUser接口又需要该参数,这种时候就可以使用参数分组来实现。

  1. 定义表示组别的interface;
public interface GroupA {
}
  1. 在@Validated中指定使用哪个组;
@RestController
public class PingController {
    @PostMapping("/getUser")
    public String getUserStr(@RequestBody @Validated({GroupA.class, Default.class}) UserInfo user, BindingResult bindingResult) {
        validData(bindingResult);
        return "name: " + user.getName() + ", age:" + user.getAge();
    }

    @PostMapping("/setUser")
    public String setUser(@RequestBody @Validated UserInfo user, BindingResult bindingResult) {
        validData(bindingResult);
        return "name: " + user.getName() + ", age:" + user.getAge();
    }

其中Default为javax.validation.groups中的类,表示参数类中其他没有分组的参数,如果没有,/getUser接口的参数校验就只会有标记了GroupA的参数校验生效。

  1. 在实体类的注解中标记这个哪个组所使用的参数;
@Data
public class UserInfo {
    @NotNull( groups = {GroupA.class}, message = "id cannot be null")
    private Integer id;

    @NotNull(message = "username cannot be null")
    private String name;

    @NotNull(message = "sex cannot be null")
    private String sex;

    @Max(value = 99L)
    private Integer age;
}

基于redis生成自增流水号(格式:标志位 + 年月日时分秒 + 自增流水号)

生成流水号第一部分:标志位 + 年月日时分秒,这个很简单,不多说,代码如下:
StringBuffer sbuffer = new StringBuffer();
        sbuffer.append("T");        //标志位
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        sbuffer.append(sdf.format(new Date()));     //年月日时分秒

第二步,调用redis的自增函数,获得流水号第二部分,自增
//设置6自增6位,用于补全操作
    private static final String STR_FORMAT = "000000";
 
 
    /**
     * redis流水号自增
     * @param key        自己设置,保存当前自增值
     * @param liveTime   在redis中的缓存时间,方法中设置单位(秒/分/天……)
     * @return
     */
    public String incr(String key, long liveTime) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        Long increment = entityIdCounter.getAndIncrement();
 
        if ((null == increment || increment.longValue() == 0) && liveTime > 0) {//初始设置过期时间
            entityIdCounter.expire(liveTime, TimeUnit.DAYS);    //设置自增值过期时间,liveTime 过期时间;TimeUnit.DAYS 过期时间单位,我这边设置为天
        }
        if (increment == 0) {           
            increment = increment + 1;
        } else if (increment > 999999){    
            increment = 1L;
        }
        //位数不够,前面补0 
        DecimalFormat df = new DecimalFormat(STR_FORMAT);    
        return df.format(increment);
    }

测试
 @Override
    public String selectTaskNo() {
        //格式:T+yyyymmddHHmiss+6位流水
        StringBuffer sbuffer = new StringBuffer();
        sbuffer.append("T");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        sbuffer.append(sdf.format(new Date()));
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String incKey = "T" + dateFormat.format(new Date());
        String no = incr(incKey, 1);
        sbuffer.append(no);
        System.out.println(">>>>>>>>>>>" + sbuffer.toString());
        return sbuffer.toString();
    }

java8 lamdba

Lambda匿名内部类修改外部变量

正常在获取list中某个属性的总和时应该使用这种语法

//求所有交易员的年龄总和

BigDecimal allwages=transactions.stream()
.map(Transaction::getWages)
.reduce(new BigDecimal(0L),BigDecimal::add);

但是实际使用中有时候有特殊情况比如要在一个已有的变量上增加值

BigDecimal alld = new BigDecimal(0L);
transactions.stream().map(Transaction::getWages)
.forEach(
a-> alld = alld.add(a)
);

此时会报错

*Cannot assign a value to final variable
*Variable used in lambda expression should be final or effectively final

无法改变final量的值

不允许在Lambda表达式中修改使用的(外部)变量
由是观之,我们将Lambda的这种变量捕获行为称之为值捕获更加确切。

在实际操作中,如果我们在Lambda体内或匿名内部类中要对外部的某些量进行操作,那么这种限制也是很容易被规避,因为即使数组是final的(即该数组不能再指向其他数组对象),里面的值依旧可变。

所以我们只要将那个需要被操作的外部变量包装进一个数组即可:

final BigDecimal[] alld = {new BigDecimal(0L)};
transactions.stream().map(Transaction::getWages)
.forEach(
a-> alld[0] = alld[0].add(a)
);

此时就可以正常访问外部变量了
Java 8 的 Lambda 表达式访问局部变量时虽然没有硬性规定要被声明为 final,但实质上是和 Java 7 一样的。
一个局部变量如果要在 Java 7/8 的匿名类或是 Java 8 的 Lambda 表达式中访问,那么这个局部变量必须是 final 的,即使没有 final 饰它也是 final 类型。

换句话说,如果在匿名类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符。

为什么 Lambda 表达式(匿名类) 不能访问非 final 的局部变量呢? 因为实例变量存在堆中,而局部变量是在栈上分配,Lambda 表达(匿名类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。

你可能感兴趣的:(java架构师学习笔记)