写点什么

如何基于 Docker 快速搭建 Springboot + Mysql + Redis 项目

作者:秦怀杂货店
  • 2021 年 12 月 17 日
  • 本文字数:13277 字

    阅读完需:约 44 分钟

[TOC]

前言

有时候我们需要快速启动一些项目,但是环境往往折腾了好久,因此弄一个可以重用的快速搭建的教程,docker简直就是这方面的神器,Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux 或 Windows 操作系统的机器上,也可以实现虚拟化。


本教程基于的前提条件:


  • 机器已经安装配置好JDK1.8,并且环境变量已经配置成功

  • Maven已经配置好,IDEA中项目使用的默认Maven也配置成功

  • 本地机器安装好Docker

  • 顺便提一句,我用navicat作为数据库可视化操作工具


项目地址:https://github.com/Damaer/DemoCode/tree/main/springboot/springdemo

项目目录

├── src :源代码|    ├── main|    |    ├── java|   |   |   ├── com.aphysia.springbootdemo|   |   |   |   ├── config:配置|   |   |   |   |   ├── RedisConfig:redis配置|   |   |   |   ├── constant:常量|   |   |   |   |   ├── RedisConfig:redis常量|   |   |   |   ├── controller:控制器|   |   |   |   ├── mapper:数据库操作接口|   |   |   |   ├── model:实体类|   |   |   |   ├── service:逻辑处理层,包括接口以及实现类|   |   |   |   |    ├── impl:接口实现类|   |   |   |   ├──util:工具类|   |   |   |   |    ├── RedisUtil:redis工具类|   |   |   |   ├──SpringdemoApplication:启动类|    |    ├── resource|   |   |   ├── mapper 数据库操作sql|   |   |   ├── application.yml:全局配置类|   |   |   ├── user.sql: 初始化mysql|    ├──  test: 测试类  ├── pom.xml :项目maven依赖关系
复制代码


整体的目录如下:


搭建项目

1. docker 安装启动 mysql 以及 redis

1.1 安装 mysql

查询mysql最新的镜像:


docker search mysql
复制代码


拉取最新的mysql版本


docker pull mysql:latest
复制代码


启动mysql,用户名root,密码123456


docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
复制代码


可以通过docker ps 查看是否安装成功


% docker psCONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS      PORTS                                                  NAMES574d30f17868   mysql     "docker-entrypoint.s…"   14 months ago   Up 2 days   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql-test
复制代码


1.2 安装 redis

查询redis的镜像


docker search redis
复制代码


拉取redis的最新镜像


% docker pull redis:latestlatest: Pulling from library/rediseff15d958d66: Pull complete 1aca8391092b: Pull complete 06e460b3ba1b: Pull complete def49df025c0: Pull complete 646c72a19e83: Pull complete db2c789841df: Pull complete Digest: sha256:619af14d3a95c30759a1978da1b2ce375504f1af70ff9eea2a8e35febc45d747Status: Downloaded newer image for redis:latestdocker.io/library/redis:latest
复制代码


docker images可以查看我们安装了哪些镜像,可以看到其实我之前也安装过redis的镜像:


% docker imagesREPOSITORY               TAG       IMAGE ID       CREATED         SIZEredis                    latest    40c68ed3a4d2   3 days ago      113MBredis                    <none>    84c5f6e03bf0   14 months ago   104MBmysql                    latest    e1d7dc9731da   14 months ago   544MBdocker/getting-started   latest    1f32459ef038   16 months ago   26.8MB
复制代码


让我们启动一下redis的容器:


% docker run -itd --name redis-test -p 6379:6379 redis7267e14faf93a0e416c39eeaaf51705dc4b6dc3507a68733c20a2609ade6dcd6
复制代码


可以看到docker里面现在有redismysql两个容器在跑了:


2. 初始化数据库

主要是创建数据库以及测试使用的数据表,初始化数据库的语句:


drop database IF EXISTS test;CREATE DATABASE test;use test;DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (  `id` int(11) NOT NULL,  `name` varchar(255) DEFAULT "",  `age` int(11) DEFAULT 0,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES (1, '李四', 11);INSERT INTO `user` VALUES (2, '王五', 11);
复制代码


初始化数据如下:


3.创建项目

在 IDEA 中,File --> New --> Project --> Spring Initializr(选择 JDK 8):



点击Next:



选择Web 下面的Spring WebSQL下面的 JDBC API,MybatisNoSQL下的Redis,也可以不选,直接在pom文件里自己加入即可:


<img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20211121213058.png" style="zoom:70%;" />


一路点Next,最后Finish,创建好之后,记得更新一下Maven,安装依赖包。

4.初始化代码

4.1 全局配置文件以及启动类

全局配置文件application.yml


server:  port: 8081spring:  #数据库连接配置  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&useSSL=false    username: root    password: 123456  redis:    host: 127.0.0.1     ## redis所在的服务器IP    port: 6379    ##密码,我这里没有设置,所以不填    password:    ## 设置最大连接数,0为无限    pool:      max-active: 8      min-idle: 0      max-idle: 8      max-wait: -1#mybatis的相关配置mybatis:  #mapper配置文件  mapper-locations: classpath:mapper/*.xml  type-aliases-package: com.aphysia.spingbootdemo.model  #开启驼峰命名  configuration:    map-underscore-to-camel-case: truelogging:  level:    root: debug
复制代码


启动类SpringdemoApplication:


import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication@MapperScan("com.aphysia.springdemo.mapper")public class SpringdemoApplication {
public static void main(String[] args) { SpringApplication.run(SpringdemoApplication.class, args); }
}
复制代码

4.2 实体类

与数据库中 user 表对应的实体类User.java:


package com.aphysia.springdemo.model;
public class User { int id; String name; int age;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }}
复制代码

4.3 Redis 工具类

Redis配置类RedisConfig


package com.aphysia.springdemo.config;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configurationpublic class RedisConfig {
@Autowired private RedisTemplate redisTemplate;
@Bean public RedisTemplate redisTemplateInit() { //设置序列化Key的实例化对象 redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置序列化Value的实例化对象 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; }}
复制代码


Redis 常量类 RedisConstant


package com.aphysia.springdemo.constant;
public class RedisConstant { public static String ALL_USER_KEY = "allUser";}
复制代码


Redis 工具类 RedisUtil:


package com.aphysia.springdemo.util;
import java.util.Collection;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
@Componentpublic class RedisUtil { @Resource private RedisTemplate<String, Object> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; }
/** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); }
/** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 删除缓存 * * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key)); } } }
//============================String=============================
/** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); }
/** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; }
}
/** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 递增 * * @param key 键 * @param delta 要增加几(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); }
/** * 递减 * * @param key 键 * @param delta 要减少几(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); }
//================================Map=================================
/** * HashGet * * @param key 键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); }
/** * 获取hashKey对应的所有键值 * * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); }
/** * HashSet * * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * HashSet 并设置时间 * * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); }
/** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); }
/** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); }
/** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); }
//============================set=============================
/** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } }
/** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } }
/** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } }
/** * 获取set缓存的长度 * * @param key 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } }
/** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } //===============================list=================================
/** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } }
/** * 获取list缓存的长度 * * @param key 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } }
/** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } }
/** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
/** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } }}
复制代码

4.4 Mysql 数据库操作

数据库的sql文件UserMapper.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.aphysia.springdemo.mapper.UserMapper">    <select id="getAllUsers" resultType="com.aphysia.springdemo.model.User">        SELECT * FROM user    </select>
<update id="updateUserAge" parameterType="java.lang.Integer"> update user set age=age+1 where id =#{id} </update></mapper>
复制代码


对应的mapper接口UserMapper.java


package com.aphysia.springdemo.mapper;
import com.aphysia.springdemo.model.User;
import java.util.List;
public interface UserMapper { List<User> getAllUsers();
int updateUserAge(Integer id);}
复制代码

4.5 Service 层

先定义一个操作User的接口类UserService,包含两个方法,查询所有的user以及更新user的年龄:


package com.aphysia.springdemo.service;
import com.aphysia.springdemo.model.User;
import java.util.List;

public interface UserService { public List<User> getAllUsers();
public void updateUserAge();}
复制代码


接口实现类UserServiceImpl为了证实 Redis 可用,我们查询所有的用户的时候,加入了 Redis 缓存,优先从 Redis 中加载数据


package com.aphysia.springdemo.service.impl;
import com.aphysia.springdemo.constant.RedisConstant;import com.aphysia.springdemo.mapper.UserMapper;import com.aphysia.springdemo.model.User;import com.aphysia.springdemo.service.UserService;import com.aphysia.springdemo.util.RedisUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;import java.util.List;
@Service("userService")public class UserServiceImpl implements UserService {
@Resource UserMapper userMapper;
@Autowired RedisUtil redisUtil;
@Override public List<User> getAllUsers() { List<User> users = (List<User>) redisUtil.get(RedisConstant.ALL_USER_KEY); if(CollectionUtils.isEmpty(users)){ users = userMapper.getAllUsers(); redisUtil.set(RedisConstant.ALL_USER_KEY,users); } return users; }
@Override @Transactional public void updateUserAge() { redisUtil.del(RedisConstant.ALL_USER_KEY); userMapper.updateUserAge(1); userMapper.updateUserAge(2); }}
复制代码

4.6 Controller 控制层

增加一个测试层TestController:


package com.aphysia.springdemo.controller;
import com.aphysia.springdemo.model.User;import com.aphysia.springdemo.service.UserService;import com.aphysia.springdemo.util.RedisUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controllerpublic class TestController {
@Autowired UserService userService;

@RequestMapping("/getUserList") @ResponseBody public List<User> getUserList() { return userService.getAllUsers(); }
@RequestMapping("/update") @ResponseBody public int update() { userService.updateUserAge(); return 1; }}
复制代码

4.7 pom 依赖

<?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.5.6</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.aphysia</groupId>    <artifactId>springdemo</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>springdemo</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> <!--mysql数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
<!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
复制代码

测试

启动项目,输入链接:http://localhost:8081/getUserList,可以获取到所有的user:



我们更新一下所有的用户年龄,调用http://localhost:8081/update,返回1



再次访问http://localhost:8081/getUserList,可以看到年龄全部都变成12:



怎么知道Redis生效了呢?最好就是debug,或者直接看控制台,我们已经开启了debug级别的日志:



还有一种方式,下载Redis-desktop-manager,可以直接可视化查看:



至此,一个demo项目就完成了,可以每次copy出来初始化使用。


【作者简介】


秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析JDBCMybatisSpringredis分布式剑指OfferLeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。


剑指Offer全部题解PDF


2020年我写了什么?


开源编程笔记

发布于: 刚刚阅读数: 3
用户头像

纵使缓慢,驰而不息。 2018.05.17 加入

慢慢走,比较快。公众号:秦怀杂货店

评论

发布
暂无评论
如何基于 Docker 快速搭建 Springboot + Mysql + Redis 项目