一些被问到的Java后端面试题
   点滴记录   0 评论   1102 浏览

一些被问到的Java后端面试题

   点滴记录   0 评论   1102 浏览

多用户进入页面修改同一条数据

场景描述如下:
用户A、B都打开一个修改数据信息页面,该数据信息为同一个实体User用户(id相同)

  1. A 将页面一个字段(userName为张三)修改为张三2然后保存
  2. B 看了1分钟将页面另一个字段(userAge为22)也改了改成21,然后保存

预期效果应该为 userName:张三2,userAge:21
因为没有做任何处理则张三2又被改回来了。出现了userName:张三,userAge:21。

三种解决办法:

  1. 打开同时锁定表的行记录
  2. 用lock对修改方法加锁
  3. 捕获错误,撤消其中一个用户的修改(判断修改时间)

具体内容:

  1. 行锁:当我们对一行进行更新但是不提交的时候,其他进程也对该行进行更新则需要进行等待。偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
    表锁:但是被锁的表可以被查询却不能增,删,改表,虽能够防止丢失更新和不可重复写这类并发问题,但是它会影响并发性能。
  2. 在业务代码中用lock对修改方法加锁,运行时注入单例,并且对方法加同步锁,保证了业务数据的正确性,但是效率低下,用户在使用中不方便,背离了系统可以并发操作的原则。
  3. 数据校验机制,不做数据库层次上的锁定。给表添加一字段:LAST_UPDATE,即最后更新时间。

A更新数据时WHERE条件里多一条件:AND LAST_UPDATE = 'xxxx-xx-xx xx:00:00',(现在的时间)
修改记录时同时将当前时间'xxxx-xx-xx xx:00:00'赋值给LAST_UPDATE字段,更新成功。
B更新数据时WHERE条件里也有这个条件,但是过去了一分钟:AND LAST_UPDATE = 'xxxx-xx-xx xx:01:00',此时LAST_UPDATE的值已经在A修改记录时变成'xxxx-xx-xx xx:00:00'
下面要做的就是给出提示:该记录已经被修改过一次,你的数据可能存在错误。此时应该临时保存B改的地方,然后重新查找给B,再让B重新保存。

模糊查询避免索引失效

# 为tid建立该表索引
select * from t where tid like '%1234%'
select * from t where tid like '1234%'
select * from t where tid like '%1234'

# EXPLAIN查看查询语句索引状态
explain select * from t where tid like '%123';

# 查看表索引
show index from t;

# 创建表索引
create index student_name on student(s_name);

问哪一个查询的索引不会失效?
如果要使用第三条语句根据tid后四位模糊查询,则需怎么做才能保证索引不失效?

如果告诉你第三条会失效,但是业务就是这么查,应该怎么办?

答案1:
数据库一般都是前缀索引,所以支持模糊匹配在后面。

答案2:
可以做个冗余列,(MySQL5.7之后加入了虚拟列,使用虚拟列更合适,思路相同),比如tid_reverse,内部存tid的倒叙存储。为tid_reverse建立索引即可,查询时用select * from t where tid_reverse like REVERSE('%1234')即可。改造后的模糊匹配可以使用tid_reverse字段上的索引,加快查询速度。

tid_reverse的更新可以用触发器解决。set new.tid_reverse = REVERSE(new.tid);

画出SpringMVC原理图可用中文


(1)客户端(浏览器)发送请求,直接请求到DispatcherServlet。
(2)DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。
(3)解析到对应的Handler后,开始由HandlerAdapter适配器处理。
(4)HandlerAdapter会根据Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
(5)处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。
(6)ViewResolver会根据逻辑View查找实际的View。
(7)DispaterServlet把返回的Model传给View。
(8)通过View返回给请求者(浏览器)。

MyBatis几级缓存?各属于哪层

一级缓存 / 二级缓存 / 自定义缓存

默认开启的一级缓存,在SqlSession层面进行缓存的,多次调用同一个查询Mapper和同一个查询方法的同一个参数而没有穿插插入更新删除,则只会进行一次数据库查询,然后把数据缓存到缓冲中,以后直接先从缓存中取出数据,不会直接去查数据库。但不同的SqlSession互相隔离,上面情况会从数据库找。

为解决这问题,要手动开启二级缓存,在namespace层面的SqlSession对象共享。
在mybatis-config.xml文件中添加<setting name="cacheEnabled" value="true"/>,​
在xxxMapper.xml文件中添加<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024" ></cache>
具体属性及参数:
​ eviction:缓存的回收策略。
​ LRU - 最近最少使用,移除最长时间不被使用的对象。
​ FIFO - 先进先出,按对象进入缓存的顺序来移除它们。
​ SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象。
​ WEAK - 弱引用,更积极地移除基于垃圾收集器和弱引用规则的对象。
​ 默认的是LRU。
​ flushInterval:缓存刷新间隔。
​ 缓存多长时间清空一次,默认不清空,设置一个毫秒值。
​ readOnly:是否只读。
​ true:只读:mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取数据,直接就会将数据在缓存中的引用交给用户 。不安全,速度快。
​ false:读写(默认):mybatis觉得获取的数据可能会被修改,mybatis会利用序列化&反序列化的技术克隆一份新的数据给你。安全,速度相对慢。
​ size:缓存存放多少个元素。
​ type:指定自定义缓存的全类名(实现Cache接口即可)。ps:要使用二级缓存,对应的POJO必须实现序列化接口 。
​ useCache="true"是否使用一级缓存,默认false。sqlSession.clearCache();只是清除当前session中的一级缓存。
​ useCache配置:如果一条句每次都需要最新的数据,就意味着每次都需要从数据库中查询数据,可以把这个属性设置为false,如:

<select id="selectUserById" resultMap="map" useCache="false">

刷新缓存(就是清空缓存)
二级缓存默认会在insert、update、delete操作后刷新缓存,可以手动配置不更新缓存,如下:

<update id="updateUserById" parameterType="User" flushCache="false" />

MyBatis的一级缓存和二级缓存都实现了cache接口,所以要实现自定义缓存而不使用MyBatis默认的缓存,那么就要定义一个类让其实现Cache接口,并在mapper.xml文件中指明缓存的类型。

package com.rawchen.cache;
import org.apache.ibatis.cache.Cache;
import java.util.concurrent.locks.ReadWriteLock;
public class RedisCache2 implements Cache {
    private String id;
    public RedisCache2(String id){
        this.id = id;
        System.out.println("当前加入缓存的namespace" + id); //com.rawchen.dao.UserDao
    }
    @Override
    public String getId() {
        return id;
    }
    //放入缓存
    @Override
    public void putObject(Object key, Object value) {
        System.out.println("key的值为: " + key );
        System.out.println("value的值为: " + value );
    }
    //在缓存中获取
    @Override
    public Object getObject(Object key) {
        return null;
    }
    //删除缓存中数据
    @Override
    public Object removeObject(Object key) {
        return null;
    }
    //清空缓存
    @Override
    public void clear() {
    }
    //缓存命中率计算
    @Override
    public int getSize() {
        return 0;
    }
    //读写锁
    @Override
    public ReadWriteLock getReadWriteLock() {
        //ReadWriteLock=====>接口
        return null;
    }
}

mapper.xml开启自定义缓存使用:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rawchen.dao.UserDao">
 
    <!--开启二级缓存 mybatis二级缓存默认全局开启 cacheEnabled true 默认值-->
   <!--type:指定自定义cache全限定类名-->
    <!--开启自定义缓存-->
    <cache type="com.rawchen.cache.RedisCache2"></cache>
    <select id="queryById" parameterType="int" resultType="com.rawchen.entity.UserEntity">
        select * from user
        <where>
            <if test="_parameter!=null">
                id=#{userId}
            </if>
        </where>
    </select>
</mapper>

Spring单例对象,怎么实现?

在Spring中,bean可以被定义为两种模式:singleton(单例)和 prototype(多例)。
singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。

<bean id="hi" class="com.rawchen.TestBean" init-method="init" scope="singleton">

简单实现饿汉式(静态变量)单例:

class Singleton {
    //1. 构造器私有化, 外部能new
    private Singleton() {
    }

    //2.本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

简单实现懒汉式(线程安全,同步方法)单例:

class Singleton {
    private static Singleton instance;
    private Singleton() {}

    //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
    //即懒汉式
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

饿汉式单例在自己被加载时就将自己实例化,如果从资源利用效率角度来讲,比懒汉式单例类稍差些。但是从速度和反应时间角度来讲,则比懒汉式要稍好些。

简单实现懒汉式(线程安全,同步方法,高效)单例:

class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}

    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
    //同时保证了效率,推荐使用
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

还有枚举与静态内部类实现单例,由于前几种单例对象不能被继承的缺点,我们可以使用另外一种特殊化的单例模式,它被称为单例注册表。

import java.util.HashMap;
Public class RegSingleton {  
    static private HashMap registry = new HashMap();  
    //静态块,在类被加载时自动执行  
    static {  
        RegSingleton rs = new RegSingleton();  
        Registry.put(rs.getClass().getName(),rs);  
    }  
    //受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点  
    protected RegSingleton(){}  
    //静态工厂方法,返回此类的唯一实例  
    public static RegSingleton getInstance(String name) {  
        if(name == null) {  
            name = "RegSingleton";  
        }if(registry.get(name) == null) {  
            try{  
                registry.put(name,Class.forName(name).newInstance());  
            }catch(Exception ex){ex.printStackTrace();}  
        }  
        return (RegSingleton)registry.get(name);  
    }  
}

Spring实现单例的源码解析:

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory {
    /**
     * 充当了Bean实例的缓存,实现方式和单例注册表相同
     */
    private final Map singletonCache = new HashMap();

    public Object getBean(String name) throws BeansException {
        return getBean(name, null, null);
    }
    ...

    public Object getBean(String name, Class requiredType, Object[] args) throws BeansException {
        //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)
        String beanName = transformedBeanName(name);
        Object bean = null;
        //手工检测单例注册表
        Object sharedInstance = null;
        //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高
        synchronized (this.singletonCache) {
            sharedInstance = this.singletonCache.get(beanName);
        }
        if (sharedInstance != null) {
             ...
            //返回合适的缓存Bean实例
            bean = getObjectForSharedInstance(name, sharedInstance);
        } else {
            ...
            //取得Bean的定义
            RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName, false);
             ...
            //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关
            //<bean id="date" class="java.util.Date" scope="singleton"/>
            //如果是单例,做如下处理
            if (mergedBeanDefinition.isSingleton()) {
                synchronized (this.singletonCache) {
                    //再次检测单例注册表
                    sharedInstance = this.singletonCache.get(beanName);
                    if (sharedInstance == null) {
                    ...
                        try {
                            //真正创建Bean实例
                            sharedInstance = createBean(beanName, mergedBeanDefinition, args);
                            //向单例注册表注册Bean实例
                            addSingleton(beanName, sharedInstance);
                        } catch (Exception ex) {
                      ...
                        } finally {
                      ...
                        }
                    }
                }
                bean = getObjectForSharedInstance(name, sharedInstance);
            }
            //如果是非单例,即prototpye,每次都要新创建一个Bean实例
            //<bean id="date" class="java.util.Date" scope="prototype"/>
            else {
                bean = createBean(beanName, mergedBeanDefinition, args);
            }
        }
    ...
        return bean;
    }
}

MySQL查询性别并更改

select id,name,
    case t.sex when '0' then '男' else '女' end as sex,
    from customer

Java创建对象的几种方式

  1. 常用的new创建对象
  2. 反射
  3. 克隆
  4. 反序列化
Class c = Class.forName("com.rawchen.JavaDemo.bean.User");
System.out.println(c.getName);
User user = (User) c.newInstance();

================

User user = new User();
Class c = user.getClass();
System.out.println(c.getName);

================

Class c = User.class;
System.out.println(c.getName);

================

public class User implements Cloneable {}
@Override
protected User clone() throws CloneNotSupportedException {
    User u = (User) super.clone();
    return u;
}

User u = new User();
u.setName("xx");
User2 u2 = u.clone();

================

public class User implements Serializable {
    private String name;
    public User(String name) {
        this.name = name;
    }
}

//序列化过程
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
User user = new User("xxx");
oos.writeObject(user);
oos.close();

//反序列化过程
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User user2 = (User) ois.readObject();
System.out.println("user2:" + user2);
ois.close();

事务的特性以及隔离级别

Atomicity Consistency Isolation Durability
原子性      一致性      隔离性      持久性

丢失更新解决办法:对行加锁,只允许并发一个更新事务。
脏读解决办法:如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可以避免该问题。
不可重复读解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。
幻读解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题。

数据库事务的隔离级别有4个,由低到高依次为Read uncommitted(未授权读取、读未提交)Read committed(授权读取、读提交)Repeatable read(可重复读取)Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。

https://www.cnblogs.com/limuzi1994/p/9684083.html

类加载过程

在加载阶段,虚拟机需要完成以下3件事情:

连接阶段:验证为了确保被加载的类的正确性。准备为了类的静态变量分配内存,并将其赋默认值。解析为了将常量池中的符号引用替换为直接引用(内存地址)的过程。

初始化:为类的静态变量赋初值。

卸载阶段:执行了System.exit()方法,或者程序正常执行结束,或者程序在执行过程中遇到了异常或错误而异常终止,或者由于操作系统出现错误而导致Java虚拟机进程终止。

https://blog.csdn.net/zhaocuit/article/details/93038538

ArrayList安全删除特定元素

//错误用法
//报错:Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
//因为你删除了元素,但是未改变迭代的下标,这样当迭代到最后一个的时候就会抛异常
for(int i = 0 , len = list.size(); i < len; i++) {
    if(list.get(i) == XXX){
        list.remove(i);
    }
}

//正确删除
for(int i = 0, len = list.size(); i < len; i++) {   
    if(list.get(i) == XXX){
        list.remove(i);
        --len;//减少一个
    }
}

//正确删除2
Iterator<String> sListIterator = list.iterator();  
while(sListIterator.hasNext()) {  
    String e = sListIterator.next();  
    if(e.equals("3")) {
        sListIterator.remove();
    }
}

本文由 RawChen 发表, 最后编辑时间为:2021-09-27 17:58
如果你觉得我的文章不错,不妨鼓励我继续写作。

发表评论
选择表情
Top