多用户进入页面修改同一条数据
场景描述如下:
用户A、B都打开一个修改数据信息页面,该数据信息为同一个实体User用户(id相同)
- A 将页面一个字段(userName为张三)修改为张三2然后保存
- B 看了1分钟将页面另一个字段(userAge为22)也改了改成21,然后保存
预期效果应该为 userName:张三2,userAge:21
因为没有做任何处理则张三2又被改回来了。出现了userName:张三,userAge:21。
三种解决办法:
- 打开同时锁定表的行记录
- 用lock对修改方法加锁
- 捕获错误,撤消其中一个用户的修改(判断修改时间)
具体内容:
- 行锁:当我们对一行进行更新但是不提交的时候,其他进程也对该行进行更新则需要进行等待。偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表锁:但是被锁的表可以被查询却不能增,删,改表,虽能够防止丢失更新和不可重复写这类并发问题,但是它会影响并发性能。 - 在业务代码中用lock对修改方法加锁,运行时注入单例,并且对方法加同步锁,保证了业务数据的正确性,但是效率低下,用户在使用中不方便,背离了系统可以并发操作的原则。
- 数据校验机制,不做数据库层次上的锁定。给表添加一字段: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创建对象的几种方式
- 常用的new创建对象
- 反射
- 克隆
- 反序列化
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
原子性 一致性 隔离性 持久性
- 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
- 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
- 隔离性是当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
- 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成。否则的话就会造成我们虽然看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。这是不允许的。
丢失更新解决办法:对行加锁,只允许并发一个更新事务。
脏读解决办法:如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可以避免该问题。
不可重复读解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。
幻读解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题。
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted(未授权读取、读未提交)
、Read committed(授权读取、读提交)
、Repeatable read(可重复读取)
、Serializable(序列化)
,这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。
类加载过程
在加载阶段,虚拟机需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
连接阶段:验证为了确保被加载的类的正确性。准备为了类的静态变量分配内存,并将其赋默认值。解析为了将常量池中的符号引用替换为直接引用(内存地址)的过程。
初始化:为类的静态变量赋初值。
卸载阶段:执行了System.exit()方法,或者程序正常执行结束,或者程序在执行过程中遇到了异常或错误而异常终止,或者由于操作系统出现错误而导致Java虚拟机进程终止。
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();
}
}
求博主写一个你这个博客的搭建教程,界面好好看啊啊啊啊,要是有一键套用的模板就好了
搭建可以参考我近期有篇搭建博客文章,去看看 https://rawchen.com/1273
Typecho + Pinghsu魔改