SpringBoot实现Java高并发秒杀系统之DAO层开发(一)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: SpringBoot实现Java高并发秒杀系统之DAO层开发(一) 秒杀系统在如今电商项目中是很常见的,最近在学习电商项目时讲到了秒杀系统的实现,于是打算使用SpringBoot框架学习一下秒杀系统(本项目基于慕课网的一套免费视频教程:Java高并发秒杀API,视频教程中讲解的很详细,非常感谢这位讲师)。

SpringBoot实现Java高并发秒杀系统之DAO层开发(一)

秒杀系统在如今电商项目中是很常见的,最近在学习电商项目时讲到了秒杀系统的实现,于是打算使用SpringBoot框架学习一下秒杀系统(本项目基于慕课网的一套免费视频教程:Java高并发秒杀API,视频教程中讲解的很详细,非常感谢这位讲师)。也是因为最近学习了SpringBoot框架(GitHub教程:SpringBoot入门之CRUD ),觉得SpringBoot框架确实比传统SSM框架方便了很多,于是更深层次练习使用SpringBoot框架,注意:SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式。 如果你熟悉了SSM框架,学习SpringBoot框架也是很Easy的。

本项目的源码请参看:springboot-seckill 如果觉得不错可以star一下哦(#^.^#)

本项目一共分为四个模块来讲解,具体的开发教程请看我的博客文章:

  • SpringBoot实现Java高并发秒杀系统之DAO层开发(一)

  • SpringBoot实现Java高并发秒杀系统之Service层开发(二)

  • SpringBoot实现Java高并发秒杀系统之Web层开发(三)

  • SpringBoot实现Java高并发秒杀系统之并发优化(四)

起步

首先我们需要搭建SpringBoot项目开发环境,IDEA搭建SpringBoot项目的具体教程请看我的:博文

如果你对SpringBoot框架或是SSM框架不熟悉,我想推荐一下我的几个小项目帮助你更好的理解:

  • SpringBoot起步之环境搭建

  • SpringBoot-Mybatis入门之CRUD

  • 手把手教你整合SSM框架

  • SSM框架入门之环境搭建


项目设计

.
├── README -- Doc文档
├── db -- 数据库约束文件
├── mvnw
├── mvnw.cmd
├── pom.xml -- 项目依赖
└── src
├── main
│   ├── java
│   │   └── cn
│   │   └── tycoding
│   │   ├── SpringbootSeckillApplication.java -- SpringBoot启动器
│   │   ├── controller -- MVC的web层
│   │   ├── dto -- 统一封装的一些结果属性,和entity类似
│   │   ├── entity -- 实体类
│   │   ├── enums -- 手动定义的字典枚举参数
│   │   ├── exception -- 统一的异常结果
│   │   ├── mapper -- Mybatis-Mapper层映射接口,或称为DAO层
│   │   ├── redis -- redis,jedis 相关配置
│   │   └── service -- 业务层
│   └── resources
│   ├── application.yml -- SpringBoot核心配置
│   ├── mapper -- Mybatis-Mapper层XML映射文件
│   ├── static -- 存放页面静态资源,可通过浏览器直接访问
│   │   ├── css
│   │   ├── js
│   │   └── lib
│   └── templates -- 存放Thymeleaf模板引擎所需的HTML,不能在浏览器直接访问
│   ├── page
│   └── public -- HTML页面公共组件(头部、尾部)
└── test -- 测试文件

SpringBoot

之前我们在SpringBoot-Mybatis入门之CRUD中已经详细讲解了SpringBoot框架的开发流程,还是觉得一句话说的特别好:SpringBoot不是对对Spring功能上的增强,而是提供了一种快速使用Spring的方式。所以用SSM阶段的知识足够了SpringBoot阶段的开发,下面我们强调一下小技巧:

  • SpringBoot不需要配置注解扫描,之前我们配置<context:component-scan>扫描可能使用注解(@Service,@Component,@Controller等)的包路径。默认创建SpringBoot项目自动生成的Application.java启动器类会自动扫描其下的所有注解。

  • SpringBoot项目中静态资源都放在resources目录下,其中static目录中的数据可以直接通过浏览器访问,多用来放CSS、JS、img,但是不用来放html页面;其中templates用来存放HTML页面,但是需要在SpringBoot的配置文件(application.yml)中配置spring.thymeleaf.prefix标识Thymeleaf模板引擎渲染的页面位置。

  • HTML页面通过Thymeleaf的加持,为HTML页面赋予了很多功能,此时的HTML页面类似于JSP页面。访问后端存入域对象(session,request…)中的数据,可以通过th:text="${key}"获得,在JS中也可以通过[[${key}]]获得。

  • Thymeleaf提供了类似JSP页面<include>的功能:public-component:<div th:fragment="header">,main-component:<div th:replace="path/header :: header">(其中path表示public-component相对于templates的路径,/header表示component文件名,最后的header表示th:fragment中定义的名称)。

pom依赖

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>

<!-- redis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

JavaBean实体类配置

此处源码请看:GitHub

Seckill.java

public class Seckill implements Serializable {

private long seckillId; //商品ID
private String title; //商品标题
private String image; //商品图片
private BigDecimal price; //商品原价格
private BigDecimal costPrice; //商品秒杀价格

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime; //创建时间

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime; //秒杀开始时间

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime; //秒杀结束时间

private long stockCount; //剩余库存数量
}

SeckillOrder.java

public class SeckillOrder implements Serializable {

private long seckillId; //秒杀到的商品ID
private BigDecimal money; //支付金额

private long userPhone; //秒杀用户的手机号

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime; //创建时间

private boolean status; //订单状态, -1:无效 0:成功 1:已付款

private Seckill seckill; //秒杀商品,和订单是一对多的关系
}

注意实体类中Date类型数据都用了@DateTimeFormat()(来自springframework)和@JsonFormat()(来自jackson)标识可以实现Controller在返回JSON数据(用@ResponseBody标识的方法或@RestController标识的类)的时候能将Date类型的参数值(经Mybatis查询得到的数据是英文格式的日期,因为实体类中是Date类型)转换为注解中指定的格式返回给页面(相当于经过了一层SimpleDateFormate)。

其次要注意在编写实体类的时候尽量养成习惯继承Serializable接口。在SeckillOrder中我们注入了Seckill类作为一个属性,目的是为了可以使用多表查询的方式从seckill_order表中查询出来对应的seckill表数据。

表设计

创建完成了SpringBoot项目,首先我们需要初始化数据库,秒杀系统的建表SQL如下:

/*
* mysql-v: 5.7.22
*/

-- 创建数据库
-- CREATE DATABASE seckill DEFAULT CHARACTER SET utf8;

DROP TABLE IF EXISTS `seckill`;
DROP TABLE IF EXISTS `seckill_order`;

-- 创建秒杀商品表
CREATE TABLE `seckill`(
`seckill_id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`title` varchar (1000) DEFAULT NULL COMMENT '商品标题',
`image` varchar (1000) DEFAULT NULL COMMENT '商品图片',
`price` decimal (10,2) DEFAULT NULL COMMENT '商品原价格',
`cost_price` decimal (10,2) DEFAULT NULL COMMENT '商品秒杀价格',
`stock_count` bigint DEFAULT NULL COMMENT '剩余库存数量',
`start_time` timestamp NOT NULL DEFAULT '1970-02-01 00:00:01' COMMENT '秒杀开始时间',
`end_time` timestamp NOT NULL DEFAULT '1970-02-01 00:00:01' COMMENT '秒杀结束时间',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`seckill_id`),
KEY `idx_start_time` (`start_time`),
KEY `idx_end_time` (`end_time`),
KEY `idx_create_time` (`end_time`)
) CHARSET=utf8 ENGINE=InnoDB COMMENT '秒杀商品表';

-- 创建秒杀订单表
CREATE TABLE `seckill_order`(
`seckill_id` bigint NOT NULL COMMENT '秒杀商品ID',
`money` decimal (10, 2) DEFAULT NULL COMMENT '支付金额',
`user_phone` bigint NOT NULL COMMENT '用户手机号',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`state` tinyint NOT NULL DEFAULT -1 COMMENT '状态:-1无效 0成功 1已付款',
PRIMARY KEY (`seckill_id`, `user_phone`) /*联合主键,保证一个用户只能秒杀一件商品*/
) CHARSET=utf8 ENGINE=InnoDB COMMENT '秒杀订单表';

解释

秒杀系统的表设计还是相对简单清晰的,这里我们只考虑秒杀系统的业务表,不涉及其他的表,所以整个系统主要涉及两张表:秒杀商品表、订单表。当然实际情况肯定不止这两张表(比如付款相关表,但是我们并未实现这个功能),也不止表中的这些字段。这里我们需要特别注意以下几点:

注意

  • 1.我这里使用的Mysql版本是5.7.22,在Mysql5.7之后timestamp默认值不能再是0000 00-00 00:00:00,具体的介绍请看:mysql官方文档。即 TIMESTAMP has a range of ‘1970-01-01 00:00:01’ UTC to ‘2038-01-19 03:14:07’ UTC.

  • 2.timestamp类型用来实现自动为新增行字段设置当前系统时间;且使用timestamp的字段必须给timestamp设置默认值,而在Mysql中date, datetime等类型都是无法实现默认设置当前系统时间值的功能(DEFAULT CURRENT_TIMESTAMP)的,所以我们必须使用timestamp类型,否则你要给字段传进来系统时间。

  • 3.decimal类型用于在数据库中设置精确的数值,比如decimal(10,2)表示可以存储10位且有2位小数的数值。

  • 4.tinyint类型用于存放int类型的数值,但是若用Mybatis作为DAO层框架,Mybatis会自动为tinyint类型的数据转换成true或false(0:false; 1 or 1+:true)。

  • 5.在订单表seckill_order中我们设计了联合主键:PRIMARY KEY (seckill_id, user_phone),目的是为了避免单个用户重复购买同一件商品(一个用户只能秒杀到一次同一件商品)。

  • 6.无论是创建数据库还是创建表我们都应该养成一个习惯就是指定character=utf-8,避免中文数据乱码;其次还应该指定表的储存引擎是InnoDB,MySQL提供了两种储存引擎:InnoDB, MyISAM。但是只有InnoDB是支持事务的,且InnoDB相比MyISAM在并发上更具有高性能的优点。

DAO层开发

DAO层是我们常说的三层架构(Web层-业务层-持久层)中与数据库交互的持久层,但是实际而言,架构是这样设计的,但是并不代表着实际项目中就一定存在一个dao文件夹,特别是现阶段我们使用的Spring-Mybatis框架。Mybatis提供了一种接口代理开发模式,也就是我们需要提供一个interface接口,其他和数据库交互的SQL编写放到对应的XML文件中(但是需要进行相关的数据库参数配置,并且Mybatis规定了使用这种开发模式必须保持接口和XML文件名称对应)。于是在本项目中就没有出现dao整个文件夹,取而代之的是mapper这个文件夹,我感觉更易识别出为Mybatis的映射接口文件。其实在实际项目中考虑到项目的大小和复杂程度,daomapper可能是同时存在的,因为service可能并不满足项目的设计,即为dao接口创建实现类,在实现类中再调用mapper接口来实现功能模块的扩展。


DAO层开发,即DAO层接口开发,主要设计需要和数据库交互的数据有哪些?应该用什么返回值类型接收查询到的数据?所以包含的方法有哪些?带着这些问题,我们先看一下秒杀系统的业务流程:

由上图可以看出,相对与本项目而言和数据库打交道的主要涉及两个操作:1.减库存(秒杀商品表);2.记录购买明细(订单表)。

  • 减库存,顾名思义就是减少当前被秒杀到的商品的库存数量,这也是秒杀系统中一个处理难点的地方。实现减库存即count-1,但是我们需要考虑Mysql的事务特性引发的种种问题、需要考虑如何避免同一用户重复秒杀的行为。

  • 如果减库存的业务解决了那么记录购买明细的业务就相对简单很多了,我们需要记录购买用户的姓名、手机号、购买的商品ID等。因为本项目中不涉及支付功能,所以记录用户的购买订单的业务并不复杂。

分析了上面的功能,下面我们开始DAO层接口的编写(源码请看:GitHub):

/**
* 减库存。
* 对于Mapper映射接口方法中存在多个参数的要加@Param()注解标识字段名称,不然Mybatis不能识别出来哪个字段相互对应
*
* @param seckillId 秒杀商品ID
* @param killTime 秒杀时间
* @return 返回此SQL更新的记录数,如果>=1表示更新成功
*/
int reduceStock(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

/**
* 插入购买订单明细
*
* @param seckillId 秒杀到的商品ID
* @param money 秒杀的金额
* @param userPhone 秒杀的用户
* @return 返回该SQL更新的记录数,如果>=1则更新成功
*/
int insertOrder(@Param("seckillId") long seckillId, @Param("money") BigDecimal money, @Param("userPhone") long userPhone);

但从接口设计上我们无非关注的就是这两个方法:1.减库存;2.插入购买明细。此处需要注意的是:

  • 对于SpringBoot系统,DAO(Mapper)层的接口需要使用@Mapper注解标识。因为SpringBoot系统中接口的XML文件不在/java目录下而是在/resources目录下。

  • 对于Mapper接口方法中存在传递多个参数的情况需要使用@Param()标识这个参数的名称,目的是为了帮助Mybatis识别传递的参数,不然Mybatis的XML中用的#{}不能识别出来你传递的参数名称是谁和谁对应的,类似于Controller层中常用的@RequestParam()注解。

  • 小技巧: 之前我们做insert和update操作时直接用void作为方法返回值,实际上虽然Mybatis的<update><select>语句并没有resultType属性,但是并不代表其没有返回值,默认返回0或1,表示执行该SQL影响的行数。为此我们可以这样写SQL,如:insert ignore into xxx用来避免Mybatis报错,而是直接返回0表示当前SQL执行失败。

  • 小技巧:因为我们必须要避免同一个用户多次抢购同一件商品,在SQL中必须限制这一点(因为即使前端怎么控制都无法避免用户多次请求同一个接口,所谓接口防刷)。所以在设计订单表的时候用了联合主键且不自增的方式,以用户ID和用户电话组成联合主键,这样当同一个用户(电话相同)多次抢购同一件商品时插入的SQL就会产生主键冲突的问题,这样就会报错。

XML映射

<update id="reduceStock">
UPDATE seckill
SET stock_count = stock_count - 1
WHERE seckill_id = #{seckillId}
AND start_time &lt;= #{killTime}
AND end_time &gt;= #{killTime}
AND stock_count &gt; 0
</update>

<insert id="insertOrder">
INSERT ignore INTO seckill_order(seckill_id, money, user_phone)
VALUES (#{seckillId}, #{money}, #{userPhone})
</insert>

SQL语句相对不是很复杂。减库存:执行update语句,令stock_count字段依次减一,并且当前要在一系列where条件的限制下;新增订单信息:保存订单数据,这里为接口防刷用联合主键seckillId, userPhone,如果同一个用户多次抢购同一件商品导致主键冲突会直接报错,为了避免系统不直接报错设计了ignore实现主键冲突就直接返回0表示该条SQL执行失败。

拓展

上面我使用了&lt;&gt;的语法其实代表的是>= <=这种符号,因为在Mybatis中编写的SQL语句如果直接使用>=<=这种判断条件可能会报错,我这里提供一种简单的解决方案就是用这种英文符号代替:

原符号 替换符号
< <
<= <=
> >
>= >=
& &
'
"

order表中findById方法

之前在SeckillOrder.java实体类中我们注入了Seckill属性,用于可以根据查询seckill_order表的同时查询到其对应的seckill表数据,对应的接口定义如下:

/**
* 根据秒杀商品ID查询订单明细数据并得到对应秒杀商品的数据,因为我们再SeckillOrder中已经定义了一个Seckill的属性
*
* @param seckillId
* @return
*/
SeckillOrder findById(long seckillId);

对应的SQL如下:

<select id="findById" resultType="SeckillOrder">
SELECT
so.seckill_id,
so.user_phone,
so.money,
so.create_time,
so.state,
s.seckill_id "seckill.seckill_id",
s.title "seckill.title",
s.cost_price "seckill.cost_price",
s.create_time "seckill.create_time",
s.start_time "seckill.start_time",
s.end_time "seckill.end_time",
s.stock_count "seckill.stock_count"
FROM seckill_order so
INNER JOIN seckill s ON so.seckill_id = s.seckill_id
WHERE so.seckill_id = #{seckillId}
</select>

这个SQL看似复杂些,但是就是仅仅的多表(两张表)查询语句:根据seckill_order表中的seckill_id字段查询seckill表中seckill_id字段值对应的数据(也就是说:对于多表查询,其实两张表之间必然存在一定的字段关联关系,不一定是外键关联,当然我们也不建议用外键关联两张表)。

其中findById的SQL中类似s.seckill_id "seckill.seckill_id"语句其实是s.seckill_id as "seckill.seckill_id",这里省略了as(别名);而INNER JOIN语句正是查询若两张表中中又相同字段的匹配值就根据两张表关联字段查询两张表的数据。这也可以使用<resultMap>中的<association>标签来实现,用于查询两张关联表的数据,如:

<resultMap id="findById" type="SeckillOrder">
<id column="seckill_id" property="seckillId"/>
<result column="user_phone" property="userPhone"/>
...
<association property="seckill" javaType="Seckill">
<id column="seckill_id" property="seckillId"/>
<result column="title" property="title"/>
...
</association>
</resultMap>

如以上也是一种映射另外一张表数据的方式(当然使用这种方式在写SQL的时候需要指定限制条件where s.seckill_id = so.seckill_id强调两张表中的seckill_id字段值相同)。

测试

在编写了Mybatis的映射接口和XML映射文件,我们可以编写一个测试类来测试一下接口和XML配置是否正确。由于我们使用IDEA开发工具,打开接口文件用快捷键Alt + Enter(我这里用的Mac系统)显示一个面板,选择Create Test快速创建本文件的测试类。

由于使用的SpringBoot框架,新创建的测试类位于/src/test/java/目录下,我们举例说明,比如创建SeckillMapper接口的测试文件:SeckillMapperTest.java

public class SeckillMapperTest {

@Autowired
private SeckillMapper seckillMapper;

@Test
public void findAll() {
}

@Test
public void findById() {
}

@Test
public void reduceStock() {
}
}

以上就是使用IDEA快捷键创建的测试类,我们仅以findAll()方法举例说明一下如何使用SpringBoot的测试类。如下:

此处的源码请参看:Github

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:application.yml")
@SpringBootTest
public class SeckillMapperTest {

@Autowired
private SeckillMapper seckillMapper;

@Test
public void findAll() {
List<Seckill> all = seckillMapper.findAll();
for (Seckill seckill : all) {
System.out.println(seckill.getTitle());
}
}

@Test
public void findById() {
}

@Test
public void reduceStock() {
}
}

SpringBoot的测试类和传统Spring框架测试类的最大区别就是不再使用@ContextConfiguration()注解去加载配置文件,取而代之的是使用@SpringBootTest注解。因为SpringBoot已经严格规定了配置文件放在resources目录下,且一般是.properties.yml结尾。如果你再使用@ContextConfiguration()注解加载配置文件反而会报错。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的服装商城管理系统
基于Java+Springboot+Vue开发的服装商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的服装商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
176 2
基于Java+Springboot+Vue开发的服装商城管理系统
|
29天前
|
XML Java 数据库连接
SpringBoot集成Flowable:打造强大的工作流管理系统
在企业级应用开发中,工作流管理是一个核心组件,它能够帮助我们定义、执行和管理业务流程。Flowable是一个开源的工作流和业务流程管理(BPM)平台,它提供了强大的工作流引擎和建模工具。结合SpringBoot,我们可以快速构建一个高效、灵活的工作流管理系统。本文将探讨如何将Flowable集成到SpringBoot应用中,并展示其强大的功能。
96 1
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
1月前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
2月前
|
存储 安全 Java
打造智能合同管理系统:SpringBoot与电子签章的完美融合
【10月更文挑战第7天】 在数字化转型的浪潮中,电子合同管理系统因其高效、环保和安全的特点,正逐渐成为企业合同管理的新宠。本文将分享如何利用SpringBoot框架实现一个集电子文件签字与合同管理于一体的智能系统,探索技术如何助力合同管理的现代化。
88 4
|
2月前
|
前端开发 Java Apache
SpringBoot实现电子文件签字+合同系统!
【10月更文挑战第15天】 在现代企业运营中,合同管理和电子文件签字成为了日常活动中不可或缺的一部分。随着技术的发展,电子合同系统因其高效性、安全性和环保性,逐渐取代了传统的纸质合同。本文将详细介绍如何使用SpringBoot框架实现一个电子文件签字和合同管理系统。
91 1
|
2月前
|
文字识别 安全 Java
SpringBoot3.x和OCR构建车牌识别系统
本文介绍了一个基于Java SpringBoot3.x框架的车牌识别系统,详细阐述了系统的设计目标、需求分析及其实现过程。利用Tesseract OCR库和OpenCV库,实现了车牌图片的识别与处理,确保系统的高准确性和稳定性。文中还提供了具体的代码示例,展示了如何构建和优化车牌识别服务,以及如何处理特殊和异常车牌。通过实际应用案例,帮助读者理解和应用这一解决方案。
|
3月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
基于Java+Springboot+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的大学竞赛报名管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
230 3
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
|
3月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的蛋糕商城管理系统
基于Java+Springboot+Vue开发的蛋糕商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的蛋糕商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
171 3
基于Java+Springboot+Vue开发的蛋糕商城管理系统
|
3月前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的美容预约管理系统
基于Java+Springboot+Vue开发的美容预约管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的美容预约管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
58 3
基于Java+Springboot+Vue开发的美容预约管理系统