到目前为止,这篇就是关于SpringBoot入门学习的最后一篇文章了,学完这三篇文章对 SpringBoot 也就有了一个初步的了解。
「 前言 」
本文的主要内容:
- 事务处理
- Docker安装及常用命令
- 接入Redis缓存及配置Session
- 整合MongoDB
- 配置开发与生产环境
- 部署项目到Docker上
「 事务处理 」
关于事务,可以简单理解为,当执行多条数据操作时,能确保每条操作能同时执行成功,否则有一条失败就会回滚前面所有执行成功的操作,保证一致性。下面我们来做一个简单的例子。
@Service
public class MyUserServices {
@Resource
private MyUserMapper userMapper;
@Transactional(rollbackFor = IllegalArgumentException.class, noRollbackFor = IllegalStateException.class)
public MyUser insertUser(MyUser user) {
userMapper.insertUser(user);
if (userMapper.selectUserByName2(user.getUserName()).size() >= 2) {
throw new IllegalArgumentException("userName " +user.getUserName() + " is all through exist");
}
if (user.getPassword().equals("123456")) {
throw new IllegalStateException("can't insert password 123456");
}
return user;
}
}
可以看到我们给inserUser
方法添加了Transactional
注解,里面包含了rollbackFor
和noRollbackFor
两个属性,通过字面上我们可以看出,一个是回滚一个是不回滚,value值都是接收异常class。可以这样理解,当方法中抛出rollbackFor
定义的异常则会执行事务回滚,当方法中抛出noRollbackFor
定义的异常则不会执行事务回滚。后面有两个判断,一个是通过判断如果插入了两个相同的用户名,则抛出异常进行回滚,实际上开发中不会这样去做,这里是为了学习事务处理来做的,另一个是通过判断密码为123456抛出异常但不进行回滚。
「 Docker安装及常用命令 」
Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似iPhone的app),更重要的是容器性能开销极低。
Docker下载地址:
Mac下载:https://download.docker.com/mac/stable/Docker.dmg
Windows下载:https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe
安装完之后,运行下面命令,验证是否安装成功
docker version
# 或者
docker info
常用的docker命令如下:
# 搜索image
docker search [imageNmae]
# 拉取 image
docker pull [imageName]
# 列出本机所有 image
docker image ls
docker images
# 查看 image 信息
docker images [imageName]
# 强制删除 image
docker rmi -f [imageId]
# 后台运行容器
docker run -d [imageName]
# 查看正在运行的容器
docker ps
# 杀掉容器
docker kill [containerId]
# 在运行的容器中执行命令
docker exec -it [containerId] [cmd]
# 强制删除容器
docker rm -f [containerId]
「 接入Redis缓存及配置Session 」
通过Docker添加Redis, 两行命令完成, pull命令时间可能会比较久,需要耐心等待。
# 拉取redis镜像文件
docker pull redis
# 运行redis容器
# -p 6379:6379: 将容器的6379端口映射到主机的6379端口
# -v $PWD/data:/data: 将主机中当前目录下的data挂载到容器的/data
# -d redis redis-server --appendonly yes: 在容器中后台执行redis-server启动命令,并打开redis持久化配置
docker run -p 6379:6379 -v $PWD/data:/data -d redis redis-server --appendonly yes
接下来添加Redis依赖
org.springframework.boot
spring-boot-starter-data-redis
配置application.yml, 添加spring cache和redis配置, 密码默认为空:
spring:
cache:
type: redis
cache-names: soaic
redis:
database: 0
host: 192.168.0.184
port: 6379
password:
jedis:
pool:
#连接池支持的最大连接数
max-active: 1000
#连接池中连接用完时,新的请求等待时间,毫秒
max-wait: -1ms
#连接池中最多可空闲maxIdle个连接
max-idle: 400
min-idle: 0
timeout: 1000ms
MyUserServices核心代码:
@Service
public class MyUserServices {
@Resource
private MyUserMapper userMapper;
@CachePut(value = "user", key = "#user.id") //如果方法参数为对象,并且不指定key,需要重写toString方法
@Transactional(rollbackFor = IllegalArgumentException.class, noRollbackFor = IllegalStateException.class)
public MyUser insertUser(MyUser user) {
userMapper.insertUser(user);
System.out.println("添加缓存key为"+user.getId());
if (userMapper.selectUserByName2(user.getUserName()).size() >= 2) {
throw new IllegalArgumentException("userName " +user.getUserName() + " is all through exist");
}
if (user.getPassword().equals("123456")) {
throw new IllegalStateException("can't insert password 123456");
}
return user;
}
@Cacheable(value = "user", key="id", condition="#id>0") //不指定key,默认以方法参数为key
public MyUser selectUser(Integer id) {
MyUser user = userMapper.selectUser(id);
System.out.println("添加缓存key为" + id);
return user;
}
@CacheEvict(value = "user")
public Integer removeUser(Integer id) {
Integer result = userMapper.deleteUser(id);
System.out.println("删除缓存key为" + id);
return result;
}
}
注解@CachePut()
表示当有缓存刷新缓存,没有则添加缓存,有三个属性,value
为缓存的名称为application.yml配置的cache-names;key
为缓存的key, 如果不指定key,默认以方法参数为key,如果方法参数为对象,需要重写toString方法;condition
为缓存的条件,条件为true时才进行缓存。
注解@Cacheable()
表示当有缓存则取缓存,没有则添加缓存,三个属性和注解@CachePut()属性一致。
注解@CacheEvict()
表示当有缓存则清除缓存,除了前面说的三个属性外,还多出两个属性,allEntries
为true清除所有元素;beforeInvocation
为true时表示在调用该方法之前清除缓存中的指定元素,默认是方法成功执行之后触发,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。
接着添加RedisCacheConfig配置key生成规则、配置RedisTemplate和缓存序列化以json格式存储
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
/**
* 配置redis key
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
for (Object obj : params) {
sb.append(":");
sb.append(obj.toString());
}
return sb.toString();
};
}
/**
* RedisTemplate 序列化
*/
@Bean
public RedisTemplate
最后把MyUser
实现Serializable
, 否则会报序列化错误
public class MyUser implements Serializable {
...
}
测试。先通过Controller(这里没有写出代码,可以到github上查看)调用MyUserServices中方法,然后进入docker运行的容器查看
# 查看正在运行的容器ID
docker ps
# 进入redis容器中
docker exec -it [containerId] redis-cli
# 进入之后输入 info 可以查看redis信息
info
# 查看所有key
keys *
# 查看某个key的值
get key
接下来就是配置Session了。为什么要用redis配置session?有这么几点原因:
1、方便管理session
2、托管到redis共享Session方便使用集群, 即一台服务器坏了,切换到另一台服务器,无需再次登录还能正常访问
首先添加依赖
org.springframework.session
spring-session-data-redis
要注意一下的是,如果spring-boot-starter-parent
版本是2.1.5.RELEASE时,添加依赖后运行会报下面这个错误,解决办法可以把版本降低,改成2.1.4.RELEASE即可。
java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration.taskScheduler
配置application.yml, 设置session存储方式
spring:
session:
store-type: redis
添加RedisSessionConfig配置,启用Session, 并设置失效时间
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)//原 Spring Boot 的 server.session.timeout 属性不再生效。
public class RedisSessionConfig {
}
接下来配置Session拦截器以及Login接口中登录成功设置session,上篇文章中介绍的还比较详细,这里就简单的贴一下核心代码:
SessionInterceptor核心代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SessionInterceptor preHandle");
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
return true;
} else {
PrintWriter printWriter = response.getWriter();
printWriter.write("{code: 501, message:\"not login!\"}");
return false;
}
}
MyUserController核心代码
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ResponseResult login(HttpServletRequest request, String userName, String password) {
ResponseResult responseResult;
try {
List myUser = myUserServices.login(userName, password);
if (myUser != null && myUser.size() > 0) {
request.getSession(true).setAttribute("user", myUser.get(0));
responseResult = new ResponseResult<>(200, "login success", myUser.get(0));
} else {
responseResult = new ResponseResult<>(501, "login failure: invalid userName or password", null);
}
} catch (Exception e) {
e.printStackTrace();
responseResult = new ResponseResult<>(501, "login failure: " + e.getMessage(), null);
}
return responseResult;
}
最后测试,当第一时间访问其它接口会报501错误,而当访问login登录成功后,则会保存session,后面其它接口访问都可以正常。这里没有用多台服务器测试,只测试了下,当服务器停止再启动后,访问接口都不用再重新登录,session存储在redis中,不会因为服务器断开而消失,这就区别于不托管redis,session是存储在内存中,每次停止服务器再启动都要重新登录。
「 整合MongoDB 」
通过Docker添加MongoDB, 同样两行命令完成。
docker pull mongo
# -p 27017:27017: 将容器的27017 端口映射到主机的27017 端口
# -v $PWD/db:/data/db: 将主机中当前目录下的db挂载到容器的/data/db,作为mongo数据存储目录
docker run -p 27017:27017 -v $PWD/db:/data/db -d mongo
添加MongoDB依赖
org.springframework.boot
spring-boot-starter-data-mongodb
配置application.yml, 这里用Docker添加的所以没有账号密码,当有账号密码时,url格式为:mongodb://user:pwd@ip:port/soaic?maxPoolSize=256; 当有多台数据库时,url格式为:mongodb://user:pwd@ip1:port1,ip2:port2/database?maxPoolSize=512
spring:
data:
mongodb:
uri: mongodb://localhost:27017/soaic?maxPoolSize=256
修改MyUser对象, 在类作用域上添加@Document
注解定义为一个文档,,也可以理解为是一张表,注解@Id
定义属性为ID, 注解@Field
定义为存储表中的字段名
@Document("myUser")
public class MyUser implements Serializable {
@Id
private String id;
private String userName;
private String password;
@Field("roles") //在文档中的名称为roles, 以数组形式存储
private Collection roles = new LinkedHashSet<>();
}
添加 Role 对象
public class Role {
private String roleName;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
在SpringBoot中对mongoDB操作数据有多种方式,如:通过继承MongoRepository
对象调用定义好的方法, 或者遵从定义方法名的规则,或者通过注解@Query
实现查询,?0
为占位符取方法参数的第一个,依次类推
public interface MyUserRepository extends MongoRepository {
List findByUserName(String name);
@Query("{'userName': ?0}")
List withQueryUserName(String name);
}
通过MongoTemplate
来实现, 下面代码简单的实现了增删改查CRUD
@Service
public class MyUserDaoImpl implements MyUserDAO{
@Autowired
MongoTemplate mongoTemplate;
@Override
public List findByUserName(String userName) {
Query query = new Query(Criteria.where("userName").is(userName));
return mongoTemplate.find(query, MyUser.class);
}
@Override
public MyUser insertUser(MyUser myUser) {
return mongoTemplate.insert(myUser);
}
@Override
public boolean deleteUser(String id) {
Query query = new Query(Criteria.where("id").is(id));
DeleteResult result = mongoTemplate.remove(query, MyUser.class);
return result.getDeletedCount() > 0;
}
@Override
public boolean updateUser(MyUser myUser) {
Criteria criteria= Criteria.where("id").is(myUser.getId());
Update update = new Update();
if (myUser.getUserName() != null)
update.set("userName", myUser.getUserName());
if (myUser.getPassword() != null)
update.set("password", myUser.getPassword());
UpdateResult result = mongoTemplate.updateFirst(new Query(criteria), update, MyUser.class);
return result.getModifiedCount() > 0;
}
}
通过单元测试对上面的代码进行测试, 在test/java/com.soaic.hellospringboot中创建MongoDBTests, 添加注解@SpringBootTest
和@RunWith(SpringRunner.class)
, 测试的方法添加@Test
注解,然后分别点击方法左边的绿色小图标 Run Test 即可
@RunWith(SpringRunner.class)
@SpringBootTest
public class MongoDBTests {
@Autowired
private MyUserRepository myUserRepository;
@Autowired
private MyUserDaoImpl myUserDaoImpl;
@Test
public void testSave() {
MyUser myUser = new MyUser();
myUser.setUserName("Soaic");
myUser.setPassword("123456");
Collection roles = new LinkedHashSet<>();
Role role = new Role();
role.setRoleName("管理员");
roles.add(role);
Role role1 = new Role();
role1.setRoleName("程序员");
roles.add(role1);
myUser.setRoles(roles);
//myUserRepository.save(myUser);
myUserDaoImpl.insertUser(myUser);
}
@Test
public void testFind() {
List myUserList = myUserDaoImpl.findByUserName("Soaic");
System.out.println(JSON.toJSONString(myUserList));
}
@Test
public void testUpdate() {
List myUserList = myUserRepository.withQueryUserName("Soaic");
for (MyUser myUser: myUserList) {
myUser.setPassword("1234567");
myUserDaoImpl.updateUser(myUser);
}
}
@Test
public void testDel() {
List myUserList = myUserRepository.findByUserName("Soaic");
for (MyUser myUser: myUserList) {
myUserDaoImpl.deleteUser(myUser.getId());
}
}
}
查询mongoDB数据库数据有无变化,可以通过如下命令连接mongoDB查询
# 连接mongodb
docker run -it mongo mongo --host 172.17.0.1
# 查看所有数据库
show dbs
# 切换数据库
use soaic
# 查看数据库状态
db.stats()
# 查询数据库下所有表
show collections
# 查询某个表的数据
db.collection.find()
「 配置开发与生产环境 」
添加application-dev.yml
开发环境配置和application-prod.yml
生产环境配置, 如果我们想使用生产环境,因为程序会默认加载application.yml
, 所以只需要在里面配置spring.profiles.active
为prod
即可,配置为dev
则为开发环境。
spring:
profiles:
active: prod
「 部署项目到Docker上 」
部署到Docker上需要先把项目打包成jar包,可以通过idea中的 Maven Projects 找到Lifecycle下的clean 和 package 依次双击执行(也可以执行命令mvn clean package
),最后就可以在target文件夹下找到hellospringboot-0.0.1-SNAPSHOT.jar
如果不部署到Docker上,可以直接运行下面命令启动项目
java -jar hellospringboot-0.0.1-SNAPSHOT.jar
如果部署到docker上,我们需要创建一个Dockerfile文件在项目的根目录,里面内容如下:
FROM java:8
MAINTAINER Soaic
ADD target/hellospringboot-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8443
EXPOSE 8088
ENTRYPOINT ["java", "-jar", "/app.jar"]
第一行:基于镜像为Java, 标签版本为8
第二行:作者Soaic
第三行:将hellospringboot-0.0.1-SNAPSHOT.jar添加到镜像中,并重命名为app.jar
第四行和第五行:运行镜像的容器,监听8443和8088端口
第六行:启动时运行 java -jar app.jar
接下来编译镜像,在项目根目录下执行下面命令,其中hellospringboot为镜像名称,最后一个".",用来指明Dockerfile路径,表示在当前路路径下,编译第一次需要下载java8,后面编译就不需要下载了
docker build -t hellospringboot .
编译完成后,可以通过下面命令查看及运行项目
# 查看是否有一个image为hellospringboot
docker images
# 后台运行项目,并映射两个端口号8443和8088,--name为修改运行容器名称可加可不加默认为image名称
docker run -d --name hellospringboot -p 8443:8443 -p 8088:8088 hellospringboot
最后可以通过 http://localhost:8088 和 https://localhost:8443 这个两个地址访问了。
本文所有源码都已放在github上:https://github.com/soaic/HelloSpringBoot
以上就是这篇文章的全部内容,希望对大家能有所帮助,如有疑问或建议欢迎大家留言交流~