Just Do Java

Java 's Blog


  • 首页

  • 分类

  • 作者

  • 归档

  • 关于

程序员鸭血粉丝又踩坑了,一次关于 Dubbo 服务 IP 注册错误的踩坑经历

发表于 2020-02-01 | 分类于 Dubbo

Hello~各位读者新年好!我是鸭血粉丝,一位经常踩坑的程序员,所谓 bug 如风,常伴吾身,hasaki~

这不最近又遇到个问题,Dubbo 服务 IP 注册错误,好了,下面进入正题。

阅读全文 »

推荐几个 Java 学习网站

发表于 2020-01-28 | 分类于 Java

粉粉昨天晚上做梦,梦见自己想好好学习一下 Java,因为平常总是 CRUD 导致自己根本没有时间学习,所以第二天粉粉怀着激动的心情不到5点就起床整理,从而给大家推荐几个不错的网站。

阅读全文 »

Java平台概述

发表于 2020-01-25 | 分类于 Java

阿粉?阿粉?阿粉?阿粉在哪里,项目经理今天发现阿粉没来,一时间很生气,心里盘算回来一定要让阿粉知道自己不是好惹的?可是阿粉去了哪里呢?阿粉受不鸟这个公司了,太 TM XXX了,阿粉出来面试了!!!阿粉心想一定要找到一个好工作!!!

阅读全文 »

定时任务莫名停止,Spring 定时任务存在 Bug???

发表于 2020-01-25 | 分类于 Spring

Hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」)。这里阿粉给大家拜个年,祝大家蒸蒸日上烫烫烫,年年有余屯屯屯。

阅读全文 »

武汉疫情,你还我爱情!

发表于 2020-01-24 | 分类于 感悟

大家好,我是鸭血粉丝,最近过年本应该十分开心但是阿粉我实在开心不起来,作为一枚适婚年龄的程序员阿粉怀着满心的期待准备过年好好相几场亲,但是却被今年的疫情狠狠的一击,彻底没希望了。啊,西湖的水,我的泪。。。

1

阅读全文 »

SpringBoot中的那些连接池

发表于 2020-01-19 | 分类于 SpringBoot

hello~各位读者新年好,我是鸭血粉丝(大家会亲切的喊我「阿粉」),是一位喜欢吃鸭血粉丝的程序员!

阅读全文 »

年底了,阿粉问问你们被催婚了么?

发表于 2020-01-19 | 分类于 程序人生

年底了,人还没到家,阿粉就已经接到了家里的催婚电话,瞬时脑补了一下非常魔性的画面,今年多大了,现在有没有对象,有对象的话,谈了多久了,是不是改结婚了!

阅读全文 »

阿粉带你3分钟看完关于树的故事

发表于 2020-01-17 | 分类于 数据结构

在计算机中,树随处可在,可说是图论和计算机科学中的重中之重,理解树的结构、树的思想和树的优异性质对于程序设计大有裨益!

阅读全文 »

又快到了跳槽季,分享一次上市公司面试经历

发表于 2020-01-17 | 分类于 Java

Hello,大家好,我是阿粉。

又到一年年底了,过完年就到了跳槽的高峰期,不少朋友应该也在摩拳擦掌了吧。

最近阿粉的朋友出去面了一圈大厂,独角兽,上市公司,积累一些面试经验,阿粉特地跟他交流了一下,获取一手面试题。

下面开始进入正文。

这次是一家做电商软件上市公司,名字就不具体介绍,其业务跟有赞类似。

这次面试基本没问业务,全部问的都是技术点。

这里说明一下,每个面试官的风格真的都不一样。阿粉之前的面试经历,基本都有会先让你介绍业务,然后再从业务抓住某些点深入问。

这种面试风格就循序渐进,在这过程中你也会慢慢进入面试节奏。

阿粉的朋友本来以为也是这种模式的,自我介绍完,就等面试官,说你介绍下项目吧。

脑子里都想好了怎么介绍了,冷不丁的,面试官,问了下 mysql 了解多吗,我们现来问下 msyql 的吧。

 ̄□ ̄||这一下子就被破功了,然后脑子一下子没跟进节奏,后面回答的问题,就乱了。

由于这次都是技术点的面试,网上都能找到答案,所以就不带解释了。

💊自我介绍

💊事务隔离等级

💊RR 隔离等级如果解决不可重复读

💊RR 有没有解决幻读,如何解决

💊Mysql 默认事务隔离等级

💊SQL 优化经验

💊为什么索引字段加函数,就不走索引了

说说这个问题吧,问到这个的时候,这个原因就很熟悉,但是就是描述不出来。

于是我就想到先跟面试官分析了一下正常索引查找流程,简单来说就是B+树有序性,二分法定位。

而这时索引字段使用函数之后,破坏这种有序性,然后就不会根据索引走了。

💊有没有碰到 mysql iops 或 cpu 占用很高

💊mysql 日志了解吗

💊 redolog 二阶段提交了解吗

💊redolog 这个二阶段相关配置了解吗

💊binlog 主从不一致有碰到过吗

💊mysql 一些配置了解吗

上面都是 mysql 相关的问题,下面开始问了一些 JVM 问题。

💊JVM 内存区域

💊三大常量池了解吗

💊堆内存结构

这个问题,刚开始听到有点懵,后来再问了一下面试官是不是想了解年轻代,老年代这些,面试回答是的。

所以球友们如果在面试中碰到没清除,或者不理解的问题,可以让面试详细说清楚问题。

不用害怕,面试是一个双向的过程。

💊GC 算法-复制算法

💊GC 算法-标记整理

💊GC 算法-标记清除

💊三种算法优缺点比较

💊老年代担保是什么

💊GC 算法中标记是什么对象

💊Full GC 触发条件

💊OOM 问题怎么排查

💊 Dump日志分析工具

下面又开始另一块问题,IO 相关问题。

💊解析一下 BIO,NIO 模型

💊Selcet 与 Epoll 的区别

💊为什么 Epoll 比较高效

💊Select 是不是同步的

💊Epoll 是不是同步

💊直接缓冲与间接缓冲区别

最后一块问题,Java 集合相关问题。

💊HashMap,CurrentHashMap 区别

💊CurrentHashMap 1.7 与 1.8 区别

💊CurrentHashMap Put 的过程

💊多线程使用的例子

💊线程池使用经验

💊线程池参数解释

💊死锁解释

💊如何排查死锁

最后其实还问了简单问题了 Redis 的用来做了什么,最后最后到了我们的问题。

你还有什么要问的吗?

常规性问题,问下自己感兴趣,技术面就不要问薪资相关的。

总结

别看阿粉的朋友现在分析头头是道,其实真正在面试的时候有些问题回答有点乱,其实心里慌了,人就会紧张,一紧张,大脑就越空白。

现在冷静下来分析,其实大部分技术点都是会的,也准备过的。

所以说面试中如何保持沉着冷静真的挺重要的,可惜这次还是慌了, ̄□ ̄||。

现在看来,其实就问技术点的面试,其实相对简单,这是因为这些都是有标准答案的,会就是会,不会就不会了。

相反,一些架构思考问题,相对来说考察不仅是你架构思维,还有平常积累,以及表达能力。

好了,今天面试题就到这了,后续再跟大家分享其他面经。

阅读全文 »

集合知识全系列回顾

发表于 2020-01-17 | 分类于 集合系列

实际开发中,经常用到的 ArrayList、LinkedList、HashMap、LinkedHashMap 等集合类,其实涵盖了很多数据结构和算法,每个类可以说都是精华,今天阿粉想和大家一起来梳理一下!

阅读全文 »

并发神器之 CopyOnWriteArrayList

发表于 2020-01-17 | 分类于 集合系列

相信大家对 ConcurrentHashMap 这个线程安全类非常熟悉,但是如果我想在多线程环境下使用 ArrayList,该怎么处理呢?阿粉今天来给你揭晓答案!

阅读全文 »

通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis核心原理

发表于 2020-01-16 | 分类于 Mybatis

Mybatis相信大家都用的很溜了,今天我鸭血粉丝来整点别的,给大家讲解一下Mybatis最核心的原理:动态生成Mapper代理对象,我会手动对整个过程进行模拟

1.平常我们是如何使用Mapper的

先写一个简单的UserMapper,它包含一个全表查询的方法,代码如下

1
2
3
4
5
1
2
3
4
5
public interface UserMapper {

    @Select("select * from user")
    public List<User> queryAll();
}

然后大家思考一个问题,我们平时是怎么使用这个UserMapper的?

很多时候我们会把Mybatis和Spring整合起来一起使用,于是会有类似下面的代码:

1
2
3
4
5
6
7
8
9
10
@Service
public class UserServiceImpl {

    @Autowired
    private UserMapper userMapper;

    public List<User> queryAll(){
        return this.userMapper.queryAll();
    }
}

看到这段熟得不能再熟的代码不知道大家会不会有一丝疑惑:UserMapper明明是一个接口,为什么可以直接调用他的queryAll方法呢?

这个问题其实也不难解,我们不能直接调用一个接口的方法,这背后肯定是有一个对象的,至于这个对象是怎么来的,这里直接告诉大家是通过动态代理生成的。只要弄懂了这个动态代理对象是怎么生成的,整个Mybatis框架原理就就说清楚了。在模拟之前我们先验证一下是不是使用JDK的动态代理。

2.验证Mybatis是通过动态代理生成Mapper代理对象

由于上面一段代码整合了spring,spring又为我们封装了许多细节,我们重新看一段代码,看看没有spring的情况下我们怎么获得一个UserMapper

1
2
3
4
5
6
7
8
public static void main(String[] args){
    
    SqlSessionFactory sqlSessionFactory = ....  //这里省略,官网给了很多配置SqlSessionFactory的方法(不一定是这么获得)

    SqlSession session = sqlSessionFactory.openSession();

    UserMapper userMapper = session.getMapper(UserMapper.class);
}

为了验证获得UserMapper采用的是动态代理,我们可以在IDE中对着session.getMapper(UserMapper.class)一路按着Ctrl点进去,我们会发现最终调用的代码是这样的:

1
2
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

最后果然调用的是JDK的动态代理

3.动手模拟一次Mybatis的动态代理

既然知道了原理,下面我们就动手验证吧!

为了简单明了,我们不写SqlSessionFactory类了,直接自定义一个MySession类,在里面给出模拟的getMapper方法:

1
2
3
4
5
6
7
8
9
10
11
public class MySession {
    public static Object getMapper(Class clazz){
        //调用newProxyInstance需要传入class数组
        Class[] clazzs = new Class[]{clazz};
        //把动态代理过程中生成的代理类保留下来,有助于新手理解动态代理(此行可省略)
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //通过动态代理生成Mapper
        Object object = Proxy.newProxyInstance(MySession.class.getClassLoader(), clazzs, new MyInvocationHandler());
        return object;
    }
}

如果不熟悉JDK的动态代理也没关系,下面我会逐步分析。

大家可以看到调用动态代理生成动态代理对象需要三个参数:

  1. 类加载器
  2. class数组
  3. InvocationHandler实例

为什么需要类加载器?

动态代理生成类和其调用类必须通过同一个类加载器加载,否则它们之间无法相互调用

为什么有个class数组?

JDK的动态代理是基于接口的,class数组中存放的是动态代理类需要实现的接口。比如本文中的例子生成的动态代理类需要实现UserMapper接口,所以你得把接口告诉它。

为什么会有InvocationHandler实例?

动态代理会在原有方法上实现增强,而增强的逻辑就写在InvocationHandler类的invoke方法上,所以要有这么个实例。

你想一想,当初的这段代码

1
2
3
4
5
1
2
3
4
5
public interface UserMapper {

    @Select("select * from user")
    public List<User> queryAll();
}

,我们想实现一个怎样的功能?

无非就是给它一条sql语句,希望它能去数据库中执行这条sql语句并返回结果。这个过程可以拆分成两个部分:

  1. 得到sql语句
  2. 通过JDBC操作数据库,并执行sql返回结果

这里省略JDBC的过程,给出一个简单的invoke方法示例(Mybatis为我们封装了一切JDBC的处理细节):

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //解析得到sql
        Select annotation = method.getAnnotation(Select.class);
        String sql = annotation.value()[0];

        //执行sql(模拟JDBC)
        System.out.println(sql + " executing...");
        return null;
    }
}

最终我们执行UserMapper的queryAll()方法时,就会出现如下结果:

1
2
3
4
5
6
7
8
9
public class Main {
    public static void main(String[] args) {
        UserMapper userMapper = (UserMapper) MySession.getMapper(UserMapper.class);
        userMapper.query();
    }
}

//打印:
//select * from user executing...

总结

最后我们总结一下Mybatis框架的核心原理:

  1. 用户只需要创建Mapper接口,并使用Mapper接口即可。
  2. Mybatis会对Mapper接口产生动态代理对象,这个动态代理对象实现了Mapper接口,拥有Mapper中定义的所有方法,并对这些方法进行了增强。增强的逻辑是获得sql语句和执行sql语句。

通过个核心原理我们也就知道了Mybatis为我们做了什么:

  1. 让方法和sql语句对应起来,操作数据库就如同调用方法一般简单
  2. 屏蔽掉JDBC的细节
阅读全文 »

Mac 上必备的常用软件,你值得拥有

发表于 2020-01-14 | 分类于 Redis

大家好,我是鸭血粉丝,最近阿粉在逛 Youtube 的时候发现了一个很有意思的视频,里面介绍了十款 Mac 上使用的软件,十分好用(原文地址:https://www.youtube.com/watch?v=WKSZXFvpu5Q)。报着好东西要一起分享的态度,阿粉准备挑几个常用的推荐给大家。

阅读全文 »

阿粉教你这样解锁单链表环的操作

发表于 2020-01-13 | 分类于 数据结构与算法

临近假期,很多人都放松了学习,阿粉一阵激动,这可是超越别人的好机会啊,赶紧去补一补数据结构和算法方面的内容。

今天阿粉教你这样解锁单链表环的操作,让你面试手写代码再也不怕!

说到单链表,肯定会想到 5 种经典操作,饭要一口一口吃,算法要一个一个拿下。今天先来讲讲在单链表中环的操作。

判断链表中是否有环

先来看一张图:

我们能够清楚的看到,在这个单链表中,是有环的。那么使用代码,该如何判断它是否有环呢?

先别着急看代码,先和阿粉一起来分析一下,有了思路,代码实现相对就比较容易。

判断链表中是否有环,可以从头结点开始,依次遍历单链表中的每一个节点。 每遍历一个节点,就和前面的所有节点作比较,如果发现新节点和之前的某个节点相同,则说明此节点被遍历过两次,说明链表有环,反之就是没有。

但是仔细看一下这种方法,你会发现这种方法很耗时耗力,因为每遍历一个节点,都要把它和前面所有的节点都比较一遍。 别着急,阿粉还有一个很巧妙的方法,就是使用两个指针。这样思路就可以这样想:

使用两个指针,一个快指针,一个慢指针。 快指针每次走 2 步,慢指针每次走 1 步。 如果链表中没有环,则快指针会先指向 null 如果链表中有环,则快慢指针一定会相遇

思路打通,咱们就可以使用代码来进行实现了:

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
/**
 * 判断链表是否有环
 */
public class IsHasLoop {
    public static class Node{
        private int data;
        private Node next;
        public Node(int data,Node next){
            this.data=data;
            this.next=next;
        }
        public int getData(){
            return data;
        }
    }
    public static void main(String[] args){
        // 初始化单链表
        Node node5=new Node(5,null);
        Node node4=new Node(4,node5);
        Node node3=new Node(3,node4);
        Node node2=new Node(2,node3);
        Node node1=new Node(1,node2);
        // 让 node5 的指针指向 node1 形成一个环
        node5.next=node1;

        boolean flag=isHasLoop(node1);
        System.out.println(flag);

    }
    public static boolean isHasLoop(Node list){
        if (list == null){
            return false;
        }

        Node slow=list;
        Node fast=list;

        while (fast.next != null && fast.next.next != null){
            // 慢指针走一步,快指针走两步
            slow=slow.next;
            fast=fast.next.next;
            // 如果快慢指针相遇,则说明链表中有环
            if (slow==fast){
                return true;
            }
        }
		// 反之链表中没有环
        return false;
    }
}

求环长

现在判断链表中是否有环这个问题已经解决了,阿粉觉得不能到此为止,思路就向外扩散了一下,既然有环了,如果想要求环长该怎么办呢?

既然快慢指针相遇了,阿粉记录下此时的位置,接下来再让满指针继续向前走,每次走 1 步,这样当慢指针再次走到相遇时的位置时,慢指针走过的长度不就是环长嘛

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
public static int getLength(Node list){
        // 定义环长初始值为 0
        int loopLength=0;
        Node slow=list;
        Node fast=list;

        while (fast != null && fast.next != null) {
            // 慢指针走一步,快指针走两步
            slow=slow.next;
            fast=fast.next.next;

            // 第一次相遇时跳出循环
            if (slow == fast) break;
        }
        // 如果 fast next 指针首先指向 null 指针,说明该链表没有环,则环长为 0
        if(fast.next == null || fast.next.next == null){
            return 0;
        }
        // 如果有环,使用临时变量保存当前的链表
        Node temp = slow;
        // 让慢指针一直走,直到走到原来位置
        do{
            slow = slow.next;
            loopLength++;
        } while(slow != temp);

        return loopLength;
}

求入环点

既然有环了,也求出了环长,那么入环点应该也知道了吧? 阿粉对于求入环点这个问题有点儿懵,就画了一张图出来:

如上图,我们假设:

入环点距离头结点距离为 D 入环点与首次相遇点较短的距离为 S1 入环点与首次相遇点较长的距离为 S2 当两个指针首次相遇时,慢指针一次只走 1 步,则它所走的距离为: D+S1 快指针每次走 2 步,多走了 n(n>=1) 圈,则它所走的距离为: D+S1+n(S1+S2) 快指针速度为慢指针的 2 倍,则: 2(D+S1)=D+S1+n(S1+S2) 上面等式,整理可得: D=(n-1)(S1+S2)+S2

如果让 (n-1)(S1+S2) 为 0 ,是不是 D 和 S2 就相等了?也就是说,当两个指针第一次相遇时,只要把其中一个指针放回到头结点位置,另外一个指针保持在首次相遇点,接下来两个指针每次都向前走 1 步,接下来这两个指针相遇时,就是要求的入环点。

有点儿像做数学题的感觉,还好阿粉的数学功底还是有那么一丢丢的。

基于上面的思路,代码就很容易实现了:

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
public static Node entryNodeOfLoop(Node list){
        Node slow=list;
        Node fast=list;
        while(fast.next != null && fast.next.next != null){
            // 慢指针走一步,快指针走两步
            slow=slow.next;
            fast=fast.next.next;

            // 第一次相遇时跳出循环
            if (slow == fast) break;
        }
        // 如果 fast next 指针首先指向 null 指针,说明该链表没有环,则入环点为 null
        if (fast.next == null || fast.next.next == null){
            return  null;
        }
        // 第一次相遇之后,让一个指针指向头结点,另外一个指针在相遇位置
        // 两个指针每次走 1 步,相遇为止,此时相遇节点即为入环点
        Node head=list; // 头结点
        Node entryNode=slow;    // 相遇节点
        while (entryNode != head){
            entryNode=entryNode.next;
            head=head.next;
        }
        return entryNode;
}

关于单链表环的操作,阿粉掌握的就是这些了。

最后总结一下:不管是求环长,还是找到入环点,最关键的就是找到第一次相遇时所在的位置,找到了这个位置,那么接下来的问题就比较容易解决了。

参考:

  • 漫画算法:小灰的算法之旅
阅读全文 »

阿粉带你搞懂事务,事务隔离级别,事务传播行为之间的关系

发表于 2020-01-12 | 分类于 数据库

大家好,今天我鸭血粉丝必须给大家宣布一个好消息:在昨晚查阅了大量资料之后,我终于搞懂了事务,事务隔离级别,以及事务传播行为三者之间的关系!

但是转头一想,独乐乐不如众乐乐,所以便有了今天这篇文章,希望大伙看完也能够理解这三者之间的关系。

事务

首先从事务这个概念开始谈,事务最开始是数据库中的概念,它把一系列的操作统一为一个整体,这一系列的操作要么同时成功,要么同时失败。

一个事务基本的操作是这样子的:

  1. 开始事务
  2. 如果发生了错误,进行回滚
  3. 如果没有发生错误,则提交事务

看到这里你可能会想,为啥会有事务???

你可以这么想:在我们处理简单业务的时候,比如说一条插入数据的操作,只会得到两个结果,要么插入成功,要么插入失败,这对应到代码逻辑上是很简单的。 我们称这样的操作具有原子性。

但是我们的业务往往不会只有插入一条数据那么简单,可能用户点击一个按钮后,我们需要插入一条数据和删除一条数据。由于每个操作都有可能成功和失败,这个时候我们就有了2^2=4种情况,这下编程起来就麻烦了。

为了方便编程(也为了符合实际业务逻辑),我们引入开头所述的事务机制,把两个操作放在一个事务里面,使这两个操作具备原子性,这样一来业务处理起来就方便多了。

事务隔离级别

说完了事务,接下来我们谈事务隔离级别。

要知道在数据库中,难免会出现多个事务同时操作数据的情况,这时数据库设置的事务隔离级别不同,会出现不同的数据操作结果,经典的脏读与幻读也诞生于此。

在逐个讲解事务隔离级别之前,阿粉先给大家看个表格,如果你没了解过事务隔离级别,可以先根据这个表格观察一下规律,然后再看后面的例子

事务隔离级别 脏读 不可重复读 幻读
read uncommitted 读未提交 会 会 会
read committed 读提交 不会 会 会
repeatable read 可重复读 不会 不会 会
serializable 串行化 不会 不会 不会

从表格中可以看到事务隔离级别越高,产生的问题越少,但是相应的性能是会降低的,下面通过几个例子分别阐述不同事务隔离级别下发生的问题:

读未提交

当事务隔离设置为读未提交时,最容易产生的问题是脏读。读未提交指的是当前事务可以读到其他事务未提交的数据。

比如说阿粉的老板要给阿粉发工资,本来要发6000块但不小心手抖多打了一个0变成60000,这时候阿粉去商场消费一查余额发现多了60000,激动万分,拼命扫货。庆幸的是老板发工资的事务还没提交,他立马回滚事务,重新发放6000块工资。

这个时候阿粉读到的余额就是脏数据,产生的原因是读取了其他事务未提交的数据。

读提交

为了解决上面提到的脏读问题,我们可以把事务隔离级别设置为读提交,他能保证当前事务只能读到其他事务已经提交的数据。但是读提交会面临一个新问题:不可重复读

比如说阿粉到商店消费,买单的时候(开启当前事务),系统检测到卡里有6000元。这个时候阿粉的女朋友(暂时给阿粉安排一个)在别的地方用阿粉的卡刷了5000元,当阿粉准备扣费的时候,再查询余额,发现只剩下了1000元(这个查询发生在女朋友消费之后)

像这样,在同一个事务中,阿粉的余额在不同的时候读取的值不一样,这就是不可重复读问题。想要解决这个问题,需要把事务隔离级别设置为可重复读

可重复读

在可重复读的情况下,当前事务会禁止其他事务对正在操作的数据进行更新,这样一来,女朋友消费的事务就要等到阿粉账号扣费结束后才能进行,从此解决了不可重复读问题。

但是可重复读级别下还可能发生一个问题叫幻读,举例如下:阿粉今天在外消费了1000元,女朋友查看阿粉今天的消费记录(开启事务),发现一共是1000元。这个时候阿粉又消费了1000元(女朋友的事务还在进行中),当女朋友打印阿粉今天的消费记录时,发现莫名其妙地变成了2000元,而且多了一条消费记录。

像这种当前事务在操作的过程中,由于别的事务增加或删除数据,导致当前事务操作的数据突然变多或变少的情况,就叫幻读。想要解决幻读,需要把事务隔离级别升级为串行化

串行化

当事务隔离级别为串行化时,所有事务都是串行执行的,对应上面的例子:女朋友在查看当天消费记录时,阿粉是不能消费的。这么一来事务并发带来的问题都能解决,但是效率很低。

扩展

在了解了事务隔离级别之后,阿粉想提醒大家:在常用的数据库中Orcale默认的事务隔离级别是读提交,而Mysql默认的是可重复读。在Mysql的InnoDB引擎中,虽然事务隔离级别是可重复读,但是也可以解决幻读问题,背后的原理是在数据行之间添加间隙锁,防止数据的插入与删除。具体选择那一种事务隔离级别,要看具体的业务需要。

事务传播行为

相信看到这里大家对事务和事务隔离级别已经有了初步的认识,再坚持把事务传播行为这小段看完就能串联起它们的关系了!

事务的传播行为从字面上也是挺好理解的:想要发生传播就一定要有两个以上的物体,而这里指的是两个方法都要在事务中进行,当一个事务方法A调用另一个事务方法B时,另一个事务方法B该如何运行。

Spring一共定义了7种事务传播行为(事务方法B该如何运行):

传播行为 含义
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中(这是最常见的选择,也是spring的默认事务传播行为)
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

平时我们最常用的是 PROPAGATION_REQUIRED,这也是spring的默认事务传播行为,理解了它就能按理推导其他的事务传播行为。

比如说当前我们有如下代码:

1
2
3
4
5
6
7
8
9
10
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
     methodB();
    // do something
}
 
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}
  1. 假设我们执行methodA,由于当前还没有事务,于是就新创建一个事务。
  2. 当在methodA中调用methodB的时候,由于methodA已经存在于事务中,于是methodB便无需新创建一个事务,直接加入到methodA的事务中即可。

总结

最后阿粉尝试通过三句话分别总结事务,事务隔离级别和事务传播行为:

  1. 事务能够让一系列不同的操作具有原子性。(当然事务具备ACID四大特性,本文在初步介绍时强调的是原子性)
  2. 事务隔离级别定义了事务并发操作时的访问规则。
  3. 事务传播行为定义了事务方法在执行时该怎么运用事务。

参考文章:
事务隔离级别:https://www.cnblogs.com/ubuntu1/p/8999403.html
事务传播行为:https://blog.csdn.net/weixin_39625809/article/details/80707695
事务传播行为:https://www.cnblogs.com/softidea/p/5962612.html

阅读全文 »

mybatis系列之初识mybatis

发表于 2020-01-12 | 分类于 Java

hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」),一位立志要成为码王的男人!

说在前面的话

今天开始,阿粉准备把 mybatis 的知识梳理一遍,为什么梳理 mybatis 呢?因为 mybatis 的源码最简单啊(说什么大实话)。no~no~no~,是因为现在的这些框架都和 springboot 整合在一起了,用起来是方便了,但是其中的原理就越不了解了。所以阿粉整理几篇 mybatis 的文章分享给大家,配合代码案例,希望大家有所收获。另外因为这是第一篇,所以代码量相对来说比较多,希望大家耐下看下去,毕竟阿粉也是下了一番功夫的,绝对是原创,如有雷同,肯定是他们抄袭阿粉的。

好了,话不多说,我们直接进入正题。

1 mybatis为我们做了哪些事情

  1. 使用连接池对连接进行管理 :连接池,这个不多说。
  2. SQL 和代码分离,集中管理 :主要是 mapper.xml 文件,专门用来配置sql语句。
  3. 重复 SQL 的提取 :比如标签。
  4. 参数映射和动态 SQL :比如 等。
  5. 结果集映射 :查询结果后会映射成对象。
  6. 缓存管理 :一级缓存和二级缓存。
  7. 插件机制:分页插件等。

这些阿粉会在 mybatis 系列文章中都会讲到。

2 准备工作(小demo,配合讲解)

先整体说下需要有哪些东西:

  • pom.xml 引入 mybatis 引入 jar 包。
  • mybatis-config.xml 配置文件。
  • mapper 接口和 mapper.xml 文件。
  • 测试类。

2.1 pom.xml 文件 ,因为不和 spring/springBoot 集成,所以引入的 jar 包比较少。

1
2
3
4
5
6
7
8
9
10
	<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</version>
    </dependency>

2.2 mybatis-config.xml文件(重点) ,db.properties 为数据库的配置信息,不单独贴出来。

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
<configuration>
    <properties resource="db.properties"></properties>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    <!-- 给实体类取别名 在mapper.xml里面就可以这样使用:resultType="fruit"-->
    <typeAliases>
        <typeAlias alias="fruit" type="com.cl.mybatis.pojo.Fruit" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置mapper.xml文件 -->
    <mappers>
        <mapper resource="FruitMapper.xml"/>
    </mappers>
</configuration>

2.3 mapper接口和 mapper.xml,Fruit为水果 pojo 类,只有id和name两个属性,不单独贴出来

1
2
3
public interface FruitMapper {
    Fruit findById(Long id);
}
1
2
3
4
5
6
7
8
<mapper namespace="com.cl.mybatis.mapper.FruitMapper">

    <sql id="fruit">id,name </sql>

    <select id="findById" resultType="fruit">
        select <include refid="fruit" /> from fruit where id =#{id}
    </select>
</mapper>

2.4 测试方法(重点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MybatisTest {
    @Test
    public void testSelect() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            FruitMapper mapper = session.getMapper(FruitMapper.class);
            Fruit fruit = mapper.findById(1L);
            System.out.println(fruit);
        } finally {
            session.close();
        }
    }
}

2.5 执行测试方法的结果

1
2
3
4
5
6
7
8
Created connection 2014838114.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7817fd62]
==>  Preparing: select id,name from fruit where id =? 
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, 苹果
<==      Total: 1
Fruit(id=1, name=苹果)

简单的 demo 就做好了,大家有兴趣的话可以跟着做一下。下面才是本次的重点类容。

3 配置文件标签作用

这里只说下mybatis-config.xml里面现在用到的标签,后面用到了其他标签时会说明。然后在最后一篇 mybatias 文章时,会把所有的标签都整理出来,并且重要的会有例子说明,还会有自定义标签里面的类容的例子。

  • configuration:这个是配置的最顶层标签
  • properties:用来配置数据库连接的信息
  • settings:全局配置标签,这里阿粉只配置了打印sql日志
  • typeAliases :类型别名,用来设置一些别名来代替 Java 的长类型声明(如 java.lang.int 变为 int),减少配置编码的冗余
  • mappers:映射器配置,配置mapper.xml文件
  • environments:环境配置,主要用来配置数据源和事物

4 核心对象

最后讲下测试类,里面的代码就是单独使用 MyBatis 的全部流程。后面讲源码也是基于这个来讲。首先说下里面的4个核心对象:

  • SqlSessionFactory 全局唯一的,一旦被创建之后,在应用的运行期间一直存在。
  • SqlSessionFactoryBuilder 这个就是用来构建 SqlSessionFactory ,一旦构建好后面就用不到。实际上就是解析 mapper.xml 和 mybatis-config.xml 里面的标签的属性值设置到 Configuration这个类对应的属性上。
  • SqlSession :是一次会话,它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个 SqlSession 对象,在请求结束或者说方法执行完毕的时候要及时关闭它。
  • Mapper :是从 SqlSession 中获取的(实际上是一个代理对象)。 它的作用是发送 SQL 来操作数据库。

然后阿粉用个类比的方法,模拟一个场景来帮助大家理解一下这几个核心对象的作用。场景是这样的:比如说一个建筑公司(SqlSessionFactory)需要修一个房子,首先是不是有设计图纸(mapper.xml)和修房子需要用到的材料清单( mybatis-config.xml),然后采购部门(SqlSessionFactoryBuilder)根据材料清单去采购材料放到仓库(Configuration)中。然后是工人师傅(SqlSession)根据设计图纸(mapper.xml)和仓库里面的材料( mybatis-config.xml)去造房子(数据库)。

这样是不是要好理解一点。啊,阿粉都被自己的聪明给折服了(自恋中)。

5 主要的执行流程

主要流程就这些,里面的细节后面会讲。这幅图的流程就不做讲解了,可以看下上面的类比例子。

6 总结

回顾一下这节的类容:

  1. 一个简单的demo。
  2. mybatis-config.xml里面一级标签的作用说明。
  3. 几个核心对象的作用。
  4. 一个图片说明了一下执行的流程。

这期 mybatis 的类容到这里就结束了,觉得阿粉讲的还可以的,记得点个赞哦!我们下期再见。

阅读全文 »

还不会用lombok?你手不痛吗

发表于 2020-01-11 | 分类于 Java

hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」),一位立志要成为码王的男人!虽然阿粉现在天天都在努力写bug,咳咳。。不是,是努力的在搬砖。因为代码量比较大,所有有bug很正常,流汗中。。。恩,阿粉可不是在解释哦。

好了,阿粉今天要带着大家来看看阿粉在搬砖途中遇到的有趣的事情

事件回顾

阿粉不是一个啰嗦的人,所以直接来看下代码

1
2
3
4
5
6
7
8
9
10
11
@Data
public class DataSourceConditionVo extends BaseConditionVo{
    private Long id;
    private String sourceName;
    private Integer status;
    private Long driverId;
    public DataSourceConditionVo(Long id,Integer status){
        this.id = id;
        this.status = status;
    }
}

大家看出有什么问题吗?阿粉悄悄的告诉大家,阿粉少写了一个无参的构造函数,导致 controller 将客户端传过来的json转成实体对象的时候报错了。

大家可不要笑话阿粉犯这么低级的错误,其实之前阿粉是有写无参构造函数的,只是看到 @Data 这个注解,阿粉以为会自动的生成无参构造函数,所以就把无参构造函数去掉了。汗。。

为了防止继续犯错,所以阿粉把lombok(lombok是什么?你还不知道?那你还不搬个板凳,买包瓜子,仔细往下面看)常用的注解都看了一遍,现在分享给大家,希望大家不要犯和阿粉一样的错误。

lombok

首先,阿粉先和大家普及一下lombok(了解过的可以快进)的相关知识。

1 什么是lombok

官方的介绍我就不复制了(是因为懒吗),主要是以简单的注解形式来简化一些没有技术含量并且又不得不写的代码,比如 get\set ,构造方法等等,提高了代码的简洁性。

2 lombok的使用

lombok的使用非常简单,只需要引入 jar 包,然后 idea 安装一个 lombok 插件。

1
2
3
4
5
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version></version>
</dependency>

version选择最新的就可以了。idea 安装插件就不介绍了,相信在座的各位都是大佬。

2.1 @Data

@Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCodetoString方法。下面通过简单的代码看下效果:

1
2
3
4
5
@Data
public class User {
    private String name;
    private Integer sex;
}

现在看下编译之后的 class 文件:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
public class User {
    private String name;
    private Integer sex;
    public User() {
    }
    public String getName() {
        return this.name;
    }
    public Integer getSex() {
        return this.sex;
    }
    public void setName(final String name) {
        this.name = name;
    }
    public void setSex(final Integer sex) {
        this.sex = sex;
    }
    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof User)) {
            return false;
        } else {
            User other = (User)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                Object this$name = this.getName();
                Object other$name = other.getName();
                if (this$name == null) {
                    if (other$name != null) {
                        return false;
                    }
                } else if (!this$name.equals(other$name)) {
                    return false;
                }
                Object this$sex = this.getSex();
                Object other$sex = other.getSex();
                if (this$sex == null) {
                    if (other$sex != null) {
                        return false;
                    }
                } else if (!this$sex.equals(other$sex)) {
                    return false;
                }
                return true;
            }
        }
    }
    protected boolean canEqual(final Object other) {
        return other instanceof User;
    }
    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.getName();
        int result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $sex = this.getSex();
        result = result * 59 + ($sex == null ? 43 : $sex.hashCode());
        return result;
    }
    public String toString() {
        return "User(name=" + this.getName() + ", sex=" + this.getSex() + ")";
    }
}

2.2 @Getter/@Setter

这个就不演示了,注解在类或字段上面,就是生成 get 和 set 方法,具体看 @Data 里面生产的 get/set 方法。

2.3 @ToString

这个也不演示了,注解在类上面,生成 toString 方法。

2.4 @EqualsAndHashCode

这个也不演示了,注解在类上面,生成 hashCode 和 equals 方法。

2.5 @NoArgsConstructor

注解在类,生成无参的构造方法。这个看下生成的代码,阿粉上面的代码就是加了这个注解就可以了。

1
2
3
4
5
6
7
public class User {
    private String name;
    private Integer sex;

    public User() {
    }
}

2.6 @RequiredArgsConstructor

注解在类上,生成包含final和@NonNull注解的成员变量的构造器。

1
2
3
4
5
6
7
8
public class User {
    private String name;
    private final Integer sex;

    public User(final Integer sex) {
        this.sex = sex;
    }
}

2.7 @AllArgsConstructor

注解在类,生成包含类中所有字段的构造方法。

1
2
3
4
5
6
7
8
9
public class User {
    private String name;
    private final Integer sex;

    public User(final String name, final Integer sex) {
        this.name = name;
        this.sex = sex;
    }
}

2.8 @Slf4j

这个也是用的比较多的,注解在类上,生成log常量

1
2
3
4
5
6
7
8
public class User {
    private static final Logger log = LoggerFactory.getLogger(User.class);
    private String name;
    private Integer sex;

    public User() {
    }
}

3 Lombok的优缺点

  1. 优点:

    a. 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率。

    b. 让代码变得简洁,不用过多的去关注相应的方法。

    c. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等。

  2. 缺点:

    a. 不支持多种参数构造器的重载。

    b. 虽然代码变得简洁,但也大大降低了代码的可读性和完整性。

4 总结

Lombok的这些知识点虽然简单,但是用得好却能大大的提高开发效率。不管别人用不用,反正阿粉早就开始用起来了。真香

一个小问题,却让阿粉了解了这么多知识,果然阿粉有成为码王的潜质。阿粉一定会努力学习,完成自己 目标。

好了,今天阿粉的分享就到这里了,我们下期再见。喜欢阿粉的伙伴记的点个赞哦!爱你,么么哒!

阅读全文 »

收集了这么多实用技巧,帮助你的 iterm2 成为最帅的那个!

发表于 2020-01-11 | 分类于 tool

1 前言

一个适合后端仔排查问题的 iterm2 终端应该是这样的:

交代下为啥要开这么多个窗口,目前阿粉我们的应用是单机部署,一个服务部署在很多台 Linux 服务器上,构建分布式架构。(实际上服务器数量比这个更多:-O)

阅读全文 »

JDK8不是只有Lambda香,还有你更想不到的呢!

发表于 2020-01-09 | 分类于 HTTP系列

继续上次的话题,阿粉昨天带着大家伙看了 Lambda表达式,是不是感觉真香,哈哈哈,今天这个绝对是更香的存在,因为之前因为阿粉用JDK7写的代码,还没老宫吐槽了很久,他是什么呢?阿粉来带大家看一下。

阅读全文 »

你还在使用JDK7,今天阿粉带你来了解一下JDK8,不得不说,真香!

发表于 2020-01-07 | 分类于 HTTP系列

前几天阿粉还在和同事抱怨,说现在 JDK 都已经11,12了,结果自己还在用 JDK 7,于是就发生了下面一幕。

阅读全文 »
1 … 15 16 17 … 31
Java Geek Tech

Java Geek Tech

一群热爱 Java 的技术人

610 日志
116 分类
24 作者
RSS
GitHub 知乎
Links
  • 纯洁的微笑
  • 沉默王二
  • 子悠
  • 江南一点雨
  • 炸鸡可乐
  • 郑璐璐
  • 程序通事
  • 懿
© 2019 - 2022 Java Geek Tech
由 Jekyll 强力驱动
主题 - NexT.Mist