Java学习笔记-Day86 Spring Boot框架(六)
一、Docker
1、Docker简介
Docker是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。
Docker 可以让开发者打包他们的应用(xx.jar)以及依赖包(jdk)到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
Docker是一个虚拟环境容器,可以将你的开发环境、代码、配置文件等一并打包到这个容器中,并发布和应用到任意平台中。比如,你在本地用Python开发网站后台,开发测试完成后,就可以将Python3及其依赖包、Flask及其各种插件、Mysql、Nginx等打包到一个容器中,然后部署到任意你想部署的环境。
2、Docke的三个概念
(1)镜像(Image):类似于虚拟机中的镜像,是一个包含有文件系统的面向Docker引擎的只读模板。任何应用程序运行都需要环境,而镜像就是用来提供这种运行环境的。例如一个Ubuntu镜像就是一个包含Ubuntu操作系统环境的模板,同理在该镜像上装上Apache软件,就可以称为Apache镜像。
(2)容器(Container):类似于一个轻量级的沙盒,可以将其看作一个极简的Linux系统环境(包括root权限、进程空间、用户空间和网络空间等),以及运行在其中的应用程序。Docker引擎利用容器来运行、隔离各个应用。容器是镜像创建的应用实例,可以创建、启动、停止、删除容器,各个容器之间是是相互隔离的,互不影响。注意:镜像本身是只读的,容器从镜像启动时,Docker在镜像的上层创建一个可写层,镜像本身不变。
(3)仓库(Repository):类似于代码仓库,这里是镜像仓库,是Docker用来集中存放镜像文件的地方。注意与注册服务器(Registry)的区别:注册服务器是存放仓库的地方,一般会有多个仓库;而仓库是存放镜像的地方,一般每个仓库存放一类镜像,每个镜像利用tag进行区分,比如Ubuntu仓库存放有多个版本(12.04、14.04等)的Ubuntu镜像。
3、Docker的安装
3.1、前提条件
目前,CentOS 仅发行版本中的内核支持 Docker。Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上。Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上,要求系统为64位、系统内核版本为 2.6.32-431 或者更高版本。
3.2、使用 yum 安装(CentOS 7下)
(1)通过 uname -r
命令查看你当前的内核版本。Docker 要求 CentOS 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。
(2)进入root用户(使用root用户登录或者使用 su
命令切换至root用户),使用 yum -y install docker
命令进行安装。
(3)使用 docker -v
命令查看docker的版本。
(4)使用 service docker start
或者 systemctl start docker.service
启动docker。
(5)测试运行hello-world:docker run hello-world
。
4、Docker的使用
4.1、常用命令
(1)查看docker的命令帮助信息:docker
或者 docker 命令 --help
(2)查看本地镜像:docker imagers
(3)运行镜像:
① docker run -d -P 镜像名
② docker run -d -p 主机端口:容器端口 镜像名
-d: 后台运行容器,并返回容器ID。
-P: 随机端口映射,容器内部端口随机映射到主机的端口。
-p: 指定端口映射,格式为:主机端口:容器端口。
(4)查看正在运行的容器:docker ps
(5)查看所有的容器列表:docker ps -a
(6)停止容器:docker stop 容器名称
(7)重启容器:docker start 容器名称
(8)删除容器:docker rm 容器名称
绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上。
4.2、测试:运行webapp镜像
(1) 从远端仓库拉取webapp镜像: docker pull training/webapp
(2)运行webapp镜像:docker run -d -P training/webapp python app.py
(3)访问网页地址:http://192.168.170.128:32768
4.3、将jar文件制作成镜像并启动
(1)创建一个Spring Boot项目,提供Spring Web支持,再创建一个控制器类,并配置application.properties文件。最后将该Spring Boot项目打包成jar包。
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>dockerdemo01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dockerdemo01</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>static/**</include>
<include>**/*.properties</include>
<include>**/*.html</include>
<include>**/*.yml</include>
<include>**/*.js</include>
</includes>
</resource>
</resources>
</build>
</project>
- application.properties
server.port=8080
- DockerController.java
package com.example.dockerdemo01.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DockerController {
@GetMapping("docker")
public String dc(){
return "Hello,Spring Boot Docker!";
}
}
点击窗口右侧的Maven -> 单击项目名 -> 单击Lifecycle -> 双击install,然后在Target目录下找到jar包。
(2)在Windows的DOS界面,对jar包进行测试。通过 jar -jar dockerdemo01-0.0.1-SNAPSHOT.jar
运行jar文件。
(3)jar包测试通过,无任何问题后,开始编写Dockerfile文件。
- Dockerfile
# Docker image for springboot file run
# VERSION 0.0.1
# Author: tom
# java8镜像
FROM java:8
# 作者
MAINTAINER tom <[email protected]>
# VOLUME 指定了临时文件目录为/tmp。
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为app.jar
ADD dockerdemo01-0.0.1-SNAPSHOT.jar app.jar
# 运行jar包
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
EXPOSE 8080
(4)在/usr/local/目录下创建一个docker文件夹,并将jar包和Dockerfile文件通过Xftp6传输到该文件夹下。
(5)制作镜像:进入docker目录下,通过 docker build -t 镜像名 .
来制作镜像。
(6)运行镜像:通过 docker run -d -p 8080:8080 镜像名
启动镜像。
(7)访问地址 192.168.170.128:8080/docker
。
二、Spring Boot锁
数据库加锁,是为了处理并发问题,在并发下保证数据一致性。虽然开发的过程中,在没有使用锁的情况下,程序也可以正常的执行,那是因为数据库隐式的添加了锁,按照使用方式可以分为:悲观锁和乐观锁;按照粒度可分为:全局锁、表锁和行锁。
悲观锁比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
1、悲观锁
1.1、悲观锁的简介
悲观锁认为拿到的数据一定会被别人修改,为了防止被人修改,当我一拿到数据,就对它进行加锁,这样别人就无法获得我的数据,只有等我的操作完成释放锁后,别人才能获得我的数据。悲观锁的实现直接在语句后面加 for update 即可。就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中的Synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
1.2、悲观锁的实现
(1)点击 New -> 点击 project -> 选择Spring Intializr -> 选择项目的Project SDK -> Choose starter service URL选择Default(https://start.spring.io)-> 点击Next。修改Java Version为对应的8版本(jdk1.8)。Web选择Spring Web,SQL选择Mybatis Framework和MySQL Driver,点击Next。输入项目名称和项目路径,点击Finish完成。
(2)创建Entity层、Dao层、Service层、Controller层的文件。悲观锁的实现直接在GoodsMapper.xml中查询语句后面加 for update 即可。
- Goods.java
package com.etc.lockdemo.entity;
import java.io.Serializable;
/**
* null
* @TableName tbl_goods
*/
public class Goods implements Serializable {
/**
* 商品序号
*/
private Integer goodsid;
/**
* 商品名
*/
private String goodsname;
/**
* 价格
*/
private Double goodsprice;
/**
* 商品简介
*/
private String goodsinfo;
/**
* 库存
*/
private Integer goodscount;
/**
* 图片地址
*/
private String cover;
/**
* 图片地址
*/
private Integer typeid;
/**
* 店铺编号
*/
private Integer shopid;
/**
* 商品状态
*/
private Integer goodsstate;
private static final long serialVersionUID = 1L;
/**
* 商品序号
*/
public Integer getGoodsid() {
return goodsid;
}
/**
* 商品序号
*/
public void setGoodsid(Integer goodsid) {
this.goodsid = goodsid;
}
/**
* 商品名
*/
public String getGoodsname() {
return goodsname;
}
/**
* 商品名
*/
public void setGoodsname(String goodsname) {
this.goodsname = goodsname;
}
/**
* 价格
*/
public Double getGoodsprice() {
return goodsprice;
}
/**
* 价格
*/
public void setGoodsprice(Double goodsprice) {
this.goodsprice = goodsprice;
}
/**
* 商品简介
*/
public String getGoodsinfo() {
return goodsinfo;
}
/**
* 商品简介
*/
public void setGoodsinfo(String goodsinfo) {
this.goodsinfo = goodsinfo;
}
/**
* 库存
*/
public Integer getGoodscount() {
return goodscount;
}
/**
* 库存
*/
public void setGoodscount(Integer goodscount) {
this.goodscount = goodscount;
}
/**
* 图片地址
*/
public String getCover() {
return cover;
}
/**
* 图片地址
*/
public void setCover(String cover) {
this.cover = cover;
}
/**
* 图片地址
*/
public Integer getTypeid() {
return typeid;
}
/**
* 图片地址
*/
public void setTypeid(Integer typeid) {
this.typeid = typeid;
}
/**
* 店铺编号
*/
public Integer getShopid() {
return shopid;
}
/**
* 店铺编号
*/
public void setShopid(Integer shopid) {
this.shopid = shopid;
}
/**
* 商品状态
*/
public Integer getGoodsstate() {
return goodsstate;
}
/**
* 商品状态
*/
public void setGoodsstate(Integer goodsstate) {
this.goodsstate = goodsstate;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Goods other = (Goods) that;
return (this.getGoodsid() == null ? other.getGoodsid() == null : this.getGoodsid().equals(other.getGoodsid()))
&& (this.getGoodsname() == null ? other.getGoodsname() == null : this.getGoodsname().equals(other.getGoodsname()))
&& (this.getGoodsprice() == null ? other.getGoodsprice() == null : this.getGoodsprice().equals(other.getGoodsprice()))
&& (this.getGoodsinfo() == null ? other.getGoodsinfo() == null : this.getGoodsinfo().equals(other.getGoodsinfo()))
&& (this.getGoodscount() == null ? other.getGoodscount() == null : this.getGoodscount().equals(other.getGoodscount()))
&& (this.getCover() == null ? other.getCover() == null : this.getCover().equals(other.getCover()))
&& (this.getTypeid() == null ? other.getTypeid() == null : this.getTypeid().equals(other.getTypeid()))
&& (this.getShopid() == null ? other.getShopid() == null : this.getShopid().equals(other.getShopid()))
&& (this.getGoodsstate() == null ? other.getGoodsstate() == null : this.getGoodsstate().equals(other.getGoodsstate()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getGoodsid() == null) ? 0 : getGoodsid().hashCode());
result = prime * result + ((getGoodsname() == null) ? 0 : getGoodsname().hashCode());
result = prime * result + ((getGoodsprice() == null) ? 0 : getGoodsprice().hashCode());
result = prime * result + ((getGoodsinfo() == null) ? 0 : getGoodsinfo().hashCode());
result = prime * result + ((getGoodscount() == null) ? 0 : getGoodscount().hashCode());
result = prime * result + ((getCover() == null) ? 0 : getCover().hashCode());
result = prime * result + ((getTypeid() == null) ? 0 : getTypeid().hashCode());
result = prime * result + ((getShopid() == null) ? 0 : getShopid().hashCode());
result = prime * result + ((getGoodsstate() == null) ? 0 : getGoodsstate().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", goodsid=").append(goodsid);
sb.append(", goodsname=").append(goodsname);
sb.append(", goodsprice=").append(goodsprice);
sb.append(", goodsinfo=").append(goodsinfo);
sb.append(", goodscount=").append(goodscount);
sb.append(", cover=").append(cover);
sb.append(", typeid=").append(typeid);
sb.append(", shopid=").append(shopid);
sb.append(", goodsstate=").append(goodsstate);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
- GoodsMapper.java
package com.etc.lockdemo.mapper;
import com.etc.lockdemo.entity.Goods;
/**
* @Entity com.etc.lockdemo.entity.Goods
*/
public interface GoodsMapper {
int deleteByPrimaryKey(Long id);
int insert(Goods record);
int insertSelective(Goods record);
Goods selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Goods record);
int updateByPrimaryKey(Goods record);
}
- GoodsMapper.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.etc.lockdemo.mapper.GoodsMapper">
<resultMap id="BaseResultMap" type="com.etc.lockdemo.entity.Goods">
<id property="goodsid" column="goodsid" jdbcType="INTEGER"/>
<result property="goodsname" column="goodsname" jdbcType="VARCHAR"/>
<result property="goodsprice" column="goodsprice" jdbcType="DOUBLE"/>
<result property="goodsinfo" column="goodsinfo" jdbcType="VARCHAR"/>
<result property="goodscount" column="goodscount" jdbcType="INTEGER"/>
<result property="cover" column="cover" jdbcType="VARCHAR"/>
<result property="typeid" column="typeid" jdbcType="INTEGER"/>
<result property="shopid" column="shopid" jdbcType="INTEGER"/>
<result property="goodsstate" column="goodsstate" jdbcType="INTEGER"/>
</resultMap>
<sql id="Base_Column_List">
goodsid,goodsname,goodsprice,
goodsinfo,goodscount,cover,
typeid,shopid,goodsstate
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tbl_goods
where goodsid = #{goodsid,jdbcType=INTEGER} for update
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from tbl_goods
where goodsid = #{goodsid,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.etc.lockdemo.entity.Goods" useGeneratedKeys="true">
insert into tbl_goods
( goodsid,goodsname,goodsprice
,goodsinfo,goodscount,cover
,typeid,shopid,goodsstate
)
values (#{goodsid,jdbcType=INTEGER},#{goodsname,jdbcType=VARCHAR},#{goodsprice,jdbcType=DOUBLE}
,#{goodsinfo,jdbcType=VARCHAR},#{goodscount,jdbcType=INTEGER},#{cover,jdbcType=VARCHAR}
,#{typeid,jdbcType=INTEGER},#{shopid,jdbcType=INTEGER},#{goodsstate,jdbcType=INTEGER}
))
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.etc.lockdemo.entity.Goods" useGeneratedKeys="true">
insert into tbl_goods
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="goodsid != null">goodsid,</if>
<if test="goodsname != null">goodsname,</if>
<if test="goodsprice != null">goodsprice,</if>
<if test="goodsinfo != null">goodsinfo,</if>
<if test="goodscount != null">goodscount,</if>
<if test="cover != null">cover,</if>
<if test="typeid != null">typeid,</if>
<if test="shopid != null">shopid,</if>
<if test="goodsstate != null">goodsstate,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="goodsid != null">goodsid = #{goodsid,jdbcType=INTEGER},</if>
<if test="goodsname != null">goodsname = #{goodsname,jdbcType=VARCHAR},</if>
<if test="goodsprice != null">goodsprice = #{goodsprice,jdbcType=DOUBLE},</if>
<if test="goodsinfo != null">goodsinfo = #{goodsinfo,jdbcType=VARCHAR},</if>
<if test="goodscount != null">goodscount = #{goodscount,jdbcType=INTEGER},</if>
<if test="cover != null">cover = #{cover,jdbcType=VARCHAR},</if>
<if test="typeid != null">typeid = #{typeid,jdbcType=INTEGER},</if>
<if test="shopid != null">shopid = #{shopid,jdbcType=INTEGER},</if>
<if test="goodsstate != null">goodsstate = #{goodsstate,jdbcType=INTEGER},</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.etc.lockdemo.entity.Goods">
update tbl_goods
<set>
<if test="goodsname != null">
goodsname = #{goodsname,jdbcType=VARCHAR},
</if>
<if test="goodsprice != null">
goodsprice = #{goodsprice,jdbcType=DOUBLE},
</if>
<if test="goodsinfo != null">
goodsinfo = #{goodsinfo,jdbcType=VARCHAR},
</if>
<if test="goodscount != null">
goodscount = #{goodscount,jdbcType=INTEGER},
</if>
<if test="cover != null">
cover = #{cover,jdbcType=VARCHAR},
</if>
<if test="typeid != null">
typeid = #{typeid,jdbcType=INTEGER},
</if>
<if test="shopid != null">
shopid = #{shopid,jdbcType=INTEGER},
</if>
<if test="goodsstate != null">
goodsstate = #{goodsstate,jdbcType=INTEGER},
</if>
</set>
where goodsid = #{goodsid,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.etc.lockdemo.entity.Goods">
update tbl_goods
set
goodsname = #{goodsname,jdbcType=VARCHAR},
goodsprice = #{goodsprice,jdbcType=DOUBLE},
goodsinfo = #{goodsinfo,jdbcType=VARCHAR},
goodscount = #{goodscount,jdbcType=INTEGER},
cover = #{cover,jdbcType=VARCHAR},
typeid = #{typeid,jdbcType=INTEGER},
shopid = #{shopid,jdbcType=INTEGER},
goodsstate = #{goodsstate,jdbcType=INTEGER}
where goodsid = #{goodsid,jdbcType=INTEGER}
</update>
</mapper>
- GoodsService.java
package com.etc.lockdemo.service;
public interface GoodsService {
public void updatePess01(long goodsid);
public void updatePess02(long goodsid);
}
- GoodsServiceImpl.java
package com.etc.lockdemo.service.impl;
import com.etc.lockdemo.entity.Goods;
import com.etc.lockdemo.mapper.GoodsMapper;
import com.etc.lockdemo.service.GoodsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GoodsServiceImpl implements GoodsService {
Logger logger = LoggerFactory.getLogger(GoodsServiceImpl.class);
@Autowired
private GoodsMapper goodsMapper;
@Override
@Transactional
public void updatePess01(long goodsid) {
Goods goods = goodsMapper.selectByPrimaryKey(goodsid);
goods.setGoodsname("updatePess01");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("-------updatePess01------");
int result = goodsMapper.updateByPrimaryKey(goods);
}
@Override
@Transactional
public void updatePess02(long goodsid) {
Goods goods = goodsMapper.selectByPrimaryKey(goodsid);
goods.setGoodsname("updatePess02");
logger.info("-------updatePess02------");
int result = goodsMapper.updateByPrimaryKey(goods);
}
}
- GoodsController.java
package com.etc.lockdemo.controller;
import com.etc.lockdemo.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GoodsController {
@Autowired
GoodsService goodsService;
@GetMapping("updatePess01/{goodsid}")
public String GetPess01(@PathVariable("goodsid") Long goodsid){
goodsService.updatePess01(goodsid);
return "updatePess01";
}
@GetMapping("updatePess02/{goodsid}")
public String GetPess02(@PathVariable("goodsid") Long goodsid){
goodsService.updatePess02(goodsid);
return "updatePess02";
}
}
- LockdemoApplication(启动类)
package com.etc.lockdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.etc.lockdemo.mapper")
public class LockdemoApplication {
public static void main(String[] args) {
SpringApplication.run(LockdemoApplication.class, args);
}
}
(3)修改application.properties配置文件。
- application.properties
#修改tomcat的端口
server.port=8080
#数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zmalldb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#指定mybatis中配置的映射文件的地址
mybatis.mapper-locations=classpath:/mapper/*.xml
##日志配置
logging.level.com.etc.lockdemo.mapper=DEBUG
logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n
logging.pattern.file=%d{yyyy/MM/dd-HH:mm} [%thread] %-5level %logger- %msg%nd] %-5level %logger- %msg%n
(4)我们先访问地址 127.0.0.1:8080/updatePess01/1,再访问地址 127.0.0.1:8080/updatePess02/1。第一个页面需要等待10秒才结束,第二个页面需要等第一个页面的请求结束后才能访问。
2、乐观锁
2.1、乐观锁的简介
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。乐观锁,大多是基于数据版本 ( Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个version字段来实现(表中添加一个字段 version)。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
2.2、乐观锁的实现
(1)点击 New -> 点击 project -> 选择Spring Intializr -> 选择项目的Project SDK -> Choose starter service URL选择Default(https://start.spring.io)-> 点击Next。修改Java Version为对应的8版本(jdk1.8)。Web选择Spring Web,SQL选择Mybatis Framework和MySQL Driver,点击Next。输入项目名称和项目路径,点击Finish完成。
(2)创建Entity层、Dao层、Service层、Controller层的文件。通过为数据库表增加一个version字段来实现实现乐观锁。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数字版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据,则sql语句会执行失败。
- Admin.java
package com.etc.lockdemo.entity;
import java.io.Serializable;
/**
* null
* @TableName tbl_admin
*/
public class Admin implements Serializable {
/**
* 管理员编号
*/
private Integer adminid;
/**
* 帐号
*/
private String adminuser;
/**
* 密码
*/
private String adminpwd;
/**
* 管理员名字
*/
private String adminname;
/**
* 加入时间
*/
private String admindate;
/**
* 管理员在线状态
*/
private Integer adminstate;
/**
*
*/
private Integer version;
private static final long serialVersionUID = 1L;
/**
* 管理员编号
*/
public Integer getAdminid() {
return adminid;
}
/**
* 管理员编号
*/
public void setAdminid(Integer adminid) {
this.adminid = adminid;
}
/**
* 帐号
*/
public String getAdminuser() {
return adminuser;
}
/**
* 帐号
*/
public void setAdminuser(String adminuser) {
this.adminuser = adminuser;
}
/**
* 密码
*/
public String getAdminpwd() {
return adminpwd;
}
/**
* 密码
*/
public void setAdminpwd(String adminpwd) {
this.adminpwd = adminpwd;
}
/**
* 管理员名字
*/
public String getAdminname() {
return adminname;
}
/**
* 管理员名字
*/
public void setAdminname(String adminname) {
this.adminname = adminname;
}
/**
* 加入时间
*/
public String getAdmindate() {
return admindate;
}
/**
* 加入时间
*/
public void setAdmindate(String admindate) {
this.admindate = admindate;
}
/**
* 管理员在线状态
*/
public Integer getAdminstate() {
return adminstate;
}
/**
* 管理员在线状态
*/
public void setAdminstate(Integer adminstate) {
this.adminstate = adminstate;
}
/**
*/
public Integer getVersion() {
return version;
}
/**
*/
public void setVersion(Integer version) {
this.version = version;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Admin other = (Admin) that;
return (this.getAdminid() == null ? other.getAdminid() == null : this.getAdminid().equals(other.getAdminid()))
&& (this.getAdminuser() == null ? other.getAdminuser() == null : this.getAdminuser().equals(other.getAdminuser()))
&& (this.getAdminpwd() == null ? other.getAdminpwd() == null : this.getAdminpwd().equals(other.getAdminpwd()))
&& (this.getAdminname() == null ? other.getAdminname() == null : this.getAdminname().equals(other.getAdminname()))
&& (this.getAdmindate() == null ? other.getAdmindate() == null : this.getAdmindate().equals(other.getAdmindate()))
&& (this.getAdminstate() == null ? other.getAdminstate() == null : this.getAdminstate().equals(other.getAdminstate()))
&& (this.getVersion() == null ? other.getVersion() == null : this.getVersion().equals(other.getVersion()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getAdminid() == null) ? 0 : getAdminid().hashCode());
result = prime * result + ((getAdminuser() == null) ? 0 : getAdminuser().hashCode());
result = prime * result + ((getAdminpwd() == null) ? 0 : getAdminpwd().hashCode());
result = prime * result + ((getAdminname() == null) ? 0 : getAdminname().hashCode());
result = prime * result + ((getAdmindate() == null) ? 0 : getAdmindate().hashCode());
result = prime * result + ((getAdminstate() == null) ? 0 : getAdminstate().hashCode());
result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", adminid=").append(adminid);
sb.append(", adminuser=").append(adminuser);
sb.append(", adminpwd=").append(adminpwd);
sb.append(", adminname=").append(adminname);
sb.append(", admindate=").append(admindate);
sb.append(", adminstate=").append(adminstate);
sb.append(", version=").append(version);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
- AdminMapper.java
package com.etc.lockdemo.mapper;
import com.etc.lockdemo.entity.Admin;
/**
* @Entity com.etc.lockdemo.entity.Admin
*/
public interface AdminMapper {
int deleteByPrimaryKey(Long id);
int insert(Admin record);
int insertSelective(Admin record);
Admin selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Admin record);
int updateByPrimaryKey(Admin record);
}
- AdminMapper.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.etc.lockdemo.mapper.AdminMapper">
<resultMap id="BaseResultMap" type="com.etc.lockdemo.entity.Admin">
<id property="adminid" column="adminid" jdbcType="INTEGER"/>
<result property="adminuser" column="adminuser" jdbcType="VARCHAR"/>
<result property="adminpwd" column="adminpwd" jdbcType="VARCHAR"/>
<result property="adminname" column="adminname" jdbcType="VARCHAR"/>
<result property="admindate" column="admindate" jdbcType="VARCHAR"/>
<result property="adminstate" column="adminstate" jdbcType="INTEGER"/>
<result property="version" column="version" jdbcType="INTEGER"/>
</resultMap>
<sql id="Base_Column_List">
adminid,adminuser,adminpwd,
adminname,admindate,adminstate,
version
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tbl_admin
where adminid = #{adminid,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from tbl_admin
where adminid = #{adminid,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.etc.lockdemo.entity.Admin" useGeneratedKeys="true">
insert into tbl_admin
( adminid,adminuser,adminpwd
,adminname,admindate,adminstate
,version)
values (#{adminid,jdbcType=INTEGER},#{adminuser,jdbcType=VARCHAR},#{adminpwd,jdbcType=VARCHAR}
,#{adminname,jdbcType=VARCHAR},#{admindate,jdbcType=VARCHAR},#{adminstate,jdbcType=INTEGER}
,#{version,jdbcType=INTEGER}))
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.etc.lockdemo.entity.Admin" useGeneratedKeys="true">
insert into tbl_admin
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="adminid != null">adminid,</if>
<if test="adminuser != null">adminuser,</if>
<if test="adminpwd != null">adminpwd,</if>
<if test="adminname != null">adminname,</if>
<if test="admindate != null">admindate,</if>
<if test="adminstate != null">adminstate,</if>
<if test="version != null">version,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="adminid != null">adminid = #{adminid,jdbcType=INTEGER},</if>
<if test="adminuser != null">adminuser = #{adminuser,jdbcType=VARCHAR},</if>
<if test="adminpwd != null">adminpwd = #{adminpwd,jdbcType=VARCHAR},</if>
<if test="adminname != null">adminname = #{adminname,jdbcType=VARCHAR},</if>
<if test="admindate != null">admindate = #{admindate,jdbcType=VARCHAR},</if>
<if test="adminstate != null">adminstate = #{adminstate,jdbcType=INTEGER},</if>
<if test="version != null">version = #{version,jdbcType=INTEGER},</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.etc.lockdemo.entity.Admin">
update tbl_admin
<set>
<if test="adminuser != null">
adminuser = #{adminuser,jdbcType=VARCHAR},
</if>
<if test="adminpwd != null">
adminpwd = #{adminpwd,jdbcType=VARCHAR},
</if>
<if test="adminname != null">
adminname = #{adminname,jdbcType=VARCHAR},
</if>
<if test="admindate != null">
admindate = #{admindate,jdbcType=VARCHAR},
</if>
<if test="adminstate != null">
adminstate = #{adminstate,jdbcType=INTEGER},
</if>
<if test="version != null">
version = #{version,jdbcType=INTEGER},
</if>
</set>
where adminid = #{adminid,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.etc.lockdemo.entity.Admin">
update tbl_admin
set
adminuser = #{adminuser,jdbcType=VARCHAR},
adminpwd = #{adminpwd,jdbcType=VARCHAR},
adminname = #{adminname,jdbcType=VARCHAR},
admindate = #{admindate,jdbcType=VARCHAR},
adminstate = #{adminstate,jdbcType=INTEGER},
version = version+1
where adminid = #{adminid,jdbcType=INTEGER} and version = #{version,jdbcType=INTEGER}
</update>
</mapper>
- AdminService.java
package com.etc.lockdemo.service;
public interface AdminService {
public void updateOpti01(long adminid);
public void updateOpti02(long adminid);
}
- AdminServiceImpl.java
package com.etc.lockdemo.service.impl;
import com.etc.lockdemo.entity.Admin;
import com.etc.lockdemo.mapper.AdminMapper;
import com.etc.lockdemo.service.AdminService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AdminServiceImpl implements AdminService {
Logger logger = LoggerFactory.getLogger(GoodsServiceImpl.class);
@Autowired
AdminMapper adminMapper;
@Override
public void updateOpti01(long adminid) {
Admin admin = adminMapper.selectByPrimaryKey(adminid);
admin.setAdminname("小明1");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("-------updateOpti01------");
adminMapper.updateByPrimaryKey(admin);
}
@Override
public void updateOpti02(long adminid) {
Admin admin = adminMapper.selectByPrimaryKey(adminid);
admin.setAdminname("小明2");
logger.info("-------updateOpti02------");
adminMapper.updateByPrimaryKey(admin);
}
}
- AdminController.java
package com.etc.lockdemo.controller;
import com.etc.lockdemo.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@Autowired
AdminService adminService;
@GetMapping("GetOpti01/{adminid}")
public String GetOpti01(@PathVariable("adminid") Long adminid){
adminService.updateOpti01(adminid);
return "updatePess01";
}
@GetMapping("GetOpti02/{adminid}")
public String GetOpti02(@PathVariable("adminid") Long adminid){
adminService.updateOpti02(adminid);
return "updatePess02";
}
}
- LockdemoApplication.java(启动类)
package com.etc.lockdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.etc.lockdemo.mapper")
public class LockdemoApplication {
public static void main(String[] args) {
SpringApplication.run(LockdemoApplication.class, args);
}
}
(3)修改application.properties配置文件。
- application.properties
#修改tomcat的端口
server.port=8080
#数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zmalldb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#指定mybatis中配置的映射文件的地址
mybatis.mapper-locations=classpath:/mapper/*.xml
##日志配置
logging.level.com.etc.lockdemo.mapper=DEBUG
logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %logger- %msg%n
logging.pattern.file=%d{yyyy/MM/dd-HH:mm} [%thread] %-5level %logger- %msg%nd] %-5level %logger- %msg%
(4)我们先访问地址 127.0.0.1:8080/GetOpti01/1,再访问地址 127.0.0.1:8080/GetOpti02/1。第一个页面需要等待10秒才结束,而第二个页面会直接响应,并更新成功。第一个页面在等待10秒后,更新失败。