分类 Develop 下的文章

背景:在业务中,经常需要在执行数据库操作后(事务提交完成),发送消息或事件来异步调用其他组件执行相应的业务操作。
比如:用户注册成功后,发送激活码或激活邮件,如果用户保存后就执行异步操作发送激活码或激活邮件,但是前面用户保存后发生异常,数据库进行回滚,用户实际没有注册成功,但用户收到激活码或激活邮件。此时,我们就迫切要求数据库事务完成后再执行异步操作。

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);

    // 发送激活码或激活邮件
    jmsProducer.sendEmail(user.getId());
}

// -------------------------------------
public void sendEmail(int userId) {
    /*
     * 获取待接收邮件的用户。(如果上面的保存用户方法还未提交事务,则实际数据还未插入到数据库中,此时会返回null)
    */
    User user = userDao.get(userId);
    // 可能抛NullPointException
    String email = user.getEmail();
    mailService.send(email);
}

解决方案

1、Spring 4.2之后,使用注解@TransactionalEventListener

/**
 * 业务Service
 */
@Service
@Transactional
public class FooService {
    @Autowired
    private  ApplicationEventPublisher applicationEventPublisher;

    public User saveUser(User user) {
        userDao.save(user);
        // 注册事件
        applicationEventPublisher.publishEvent(new SavedUserEvent(user.getId()));
    }
}

// -------------------------------------
/**
 * 保存用户事件
 */
public class SavedUserEvent {
    private int userId;
    
    public SavedUserEvent(int userId) {
        this.userId = userId;
    }
    
    // getter and setter
}

// ---------------------------------
/**
 * 事件侦听,处理对应事件
 */
@Component
public class FooEventListener() {
    @Autowired
    private UserDao userDao;
    @Autowired
    private MailService mailService;

    @TransactionalEventListener
    public sendEmail(SavedUserEvent savedUserEvent) {
        User user = userDao.get(userId);
        String email = user.getEmail();
        mailService.send(email);
    }
}

使用TransactionSynchronizationManager 和 TransactionSynchronizationAdapter

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 事务提交后调用
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            jmsProducer.sendEmail(userId);
        }
    });
}

注意:上面的代码将在事务提交后执行.如果在非事务context中将抛出java.lang.IllegalStateException: Transaction synchronization is not active。
改进后代码:

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 兼容无论是否有有事
    if(TransactionSynchronizationManager.isActualTransactionActive()) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                jmsProducer.sendEmail(userId);
            }
        });
    } else {
        jmsProducer.sendEmail(userId);
    }
}

参考文章

Spring的TransactionEventListener
Spring Event 事件中的事务控制
Java Code Examples org.springframework.transaction.support.TransactionSynchronizationAdapter
Spring Events | Baeldung

散布于应用中的多处的 功能被称为横切关注点。
通过依赖注入(DI),可以对应用中的对象之间进行解耦;通过AOP,可以将关注点与它们所影响的对象之间进行解耦。比如场景:事务、安全、缓存等。
在面向切面编程时,我们可以在集中一个地方定义通用功能,可以通过声明的方式定义这个功能要以何种方式在何处应用,而不用修改受影响的类。

Spring只支持方法级别的连接点

- 阅读剩余部分 -

最近将之前关于Spring的书重新拿出来温故一番,并进行总结。

Bean容器

Spring的Bean容器其实不止一个,归结起来可以分为2类:

  • Bean工厂(由org.springframework.beans.factory.BeanFactory接口定义),是最简单的Bean容器,提供基本的DI支持;
  • 应用上下文(由org.springframework.context.ApplicationContext接口定义),基于BeanFactory构建,提供应用框架级别的服务。

常用应用上下文

  • AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文;
  • AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文;
  • ClassPathXmlApplicationContext:从类路径下的一个或多个Spring的XML配置文件中加载应用上下文;
  • FileSystemXmlApplicationContext:从文件系统下的一个或多个Spring的XML配置文件中加载应用上下文;
  • XmlWebApplicationContext:从Web应用下的一个或多个Spring的XML配置文件中加载应用上下文。

- 阅读剩余部分 -

XML文档树图

xml_nodetree.gif

<?xml version="1.0" encoding="utf-8"?>
<bookstore> 
  <book category="COOKING"> 
    <title lang="en">Everyday Italian</title>  
    <author>Giada De Laurentiis</author>  
    <year>2005</year>  
    <price>30.00</price> 
  </book>  
  <book category="CHILDREN"> 
    <title lang="en">Harry Potter</title>  
    <author>J K. Rowling</author>  
    <year>2005</year>  
    <price>29.99</price> 
  </book>  
  <book category="WEB"> 
    <title lang="en">Learning XML</title>  
    <author>Erik T. Ray</author>  
    <year>2003</year>  
    <price>39.95</price> 
  </book> 
</bookstore>

一个xml文档由元素节点,属性节点,文本节点构成,其中bookstore被称为文档元素或根元素,也是一个元素节点。XML 文档中的每个成分都是一个节点Node

- 阅读剩余部分 -

全国行政区域数据库

SQL脚本:

CREATE TABLE `area` (
  `id` int(11) NOT NULL,
  `name` varchar(50) NOT NULL COMMENT '名称',
  `abbr` varchar(50) NOT NULL COMMENT '简称',
  `sortBy` int(3) DEFAULT '0' COMMENT '排序',
  `level` int(1) DEFAULT NULL COMMENT '等级(1省/直辖市,2地级市,3区县,4镇/街道)',
  `longitude` varchar(50) DEFAULT NULL COMMENT '经度',
  `latitude` varchar(50) DEFAULT NULL COMMENT '纬度',
  `parentId` int(11) DEFAULT NULL COMMENT '父级ID',
  PRIMARY KEY (`id`),
  KEY `fk_area_parent` (`parentId`),
  CONSTRAINT `fk_area_parent` FOREIGN KEY (`parentId`) REFERENCES `area` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

- 阅读剩余部分 -

Intellij IDEA功能很强大,而且是越用越爱不释手的,下文用IDEA来表示:Intellij IDEA。

自动生成serialVersionUID

默认IDEA是关闭了继承Serializable接口类生成serialVersionUID的警告,如果要让IDEA自动生成,需要进行如下操作:

  1. Files → Settings → Editor → Inspections → Serializable class without 'serialVersionUID',勾选上。默认提醒级别是:Warning,可以根据需要设置成:Error。

IDEA_serialVersionUID.jpg

  1. 将光标放到类名上,按Atl + Enter键,就会提示生成serialVersionUID了。

关闭自动保存和标志修改文件为星号

IDEA默认自动保存文件,而且及时修改了文件也没有*标记。

- 阅读剩余部分 -

Maven运行参数

运行VM参数

-Xms128m -Xmx256m -Xmn32m -XX:PermSize=32M -XX:MaxPermSize=256m
  • XmsXmx:程序运行进程堆内存能占用的最小大小和最大大小;
  • Xmn:用来设置堆内新生代的大小。通过这个值我们也可以得到老生代的大小:-Xmx减去-Xmn;
  • Xss:设置每个线程可使用的内存大小
  • XX:PermSizeXX:MaxPermSize:设置永久代的最小大小和最大大小。注意:Java 8移除该参数,而是用本地元空间Metaspace,大小由-XX:MetaspaceSize-XX:MaxMetaspaceSize进行设置。

常规参数

  • 是否离线:-o
  • 是否更新Snapshots:-U
  • Debug Output:-X -e
  • 是否忽略test:-Dmaven.test.skip=true

Jetty

  • 指定端口: -Djetty.port=xxx(更改xxx设置为自定义端口,比如:8081)

Intellij IDEA常用快捷整理表

Ctrl

快捷键说明
Ctrl + F当前文件进行文本查找
Ctrl + R当前文件进行文本替换
Ctrl + Z撤销
Ctrl + Y删除光标所在行 或 删除选中行
Ctrl + X剪切光标所在行 或 剪切选择内容
Ctrl + C复制光标所在行 或 复制选择内容
Ctrl + D复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面
Ctrl + W递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围
Ctrl + E显示最近打开的文件记录列表
Ctrl + N根据输入名称查找Class类文件
Ctrl + G跳转到指定行
Ctrl + J插入自定义动态代码模板
Ctrl + P方法参数提示显示
Ctrl + Q光标所在的变量 / 类名 / 方法名等上面(也可以在提示补充的时候按),显示文档内容
Ctrl + U前往当前光标所在的方法的父类的方法 / 接口定义
Ctrl + B进入光标所在的方法/变量的接口或是定义处,等效于Ctrl + 左键单击
Ctrl + K版本控制提交项目,需要此项目有加入到版本控制才可用
Ctrl + T版本控制更新项目,需要此项目有加入到版本控制才可用
Ctrl + H显示当前类的层次结构
Ctrl + O选择可重写的方法
Ctrl + I选择可继承的方法
Ctrl + +展开代码
Ctrl + -折叠代码
Ctrl + /注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
Ctrl + [移动光标到当前所在代码的花括号开始位置
Ctrl + ]移动光标到当前所在代码的花括号结束位置
Ctrl + F1在光标所在的错误代码处显示错误信息
Ctrl + F3调转到所选中的词的下一个引用位置
Ctrl + F4关闭当前编辑文件
Ctrl + F8Debug模式下,设置光标当前行为断点,如果当前已经是断点则去掉断点
Ctrl + F9执行 Make Project 操作
Ctrl + F11选中文件 / 文件夹,使用助记符设定 / 取消书签
Ctrl + F12弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选
Ctrl + Tab编辑窗口切换,如果在切换的过程又加按上delete,则是关闭对应选中的窗口
Ctrl + End跳到文件尾
Ctrl + Home跳到文件头
Ctrl + Space基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + ,Alt + /
Ctrl + Delete删除光标后面的单词或是中文句
Ctrl + BackSpace删除光标前面的单词或是中文句
Ctrl + 1,2,3...,9定位到对应数值的书签位置
Ctrl + 左键单击在打开的文件标题上,弹出该文件路径
Ctrl + 光标定位按 Ctrl 不要松开,会显示光标所在的类信息摘要
Ctrl + ←光标跳转到当前单词 / 中文句的左侧开头位置
Ctrl + →光标跳转到当前单词 / 中文句的右侧开头位置
Ctrl + ↑等效于鼠标滚轮向前效果
Ctrl + ↓等效于鼠标滚轮向后效果

- 阅读剩余部分 -