本文共 13505 字,大约阅读时间需要 45 分钟。
前言
本文的主要内容:
事务处理
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 version 或者docker info常用的docker命令如下: 搜索imagedocker search [imageNmae] 拉取 imagedocker pull [imageName] 列出本机所有 imagedocker image lsdocker images 查看 image 信息docker images [imageName] 强制删除 imagedocker 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依赖配置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: 1000msMyUserServices核心代码:@Servicepublic 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; }} org.springframework.boot spring-boot-starter-data-redis
注解 @CachePut()表示当有缓存刷新缓存,没有则添加缓存,有三个属性, value为缓存的名称为application.yml配置的cache-names; key为缓存的key, 如果不指定key,默认以方法参数为key,如果方法参数为对象,需要重写toString方法; condition为缓存的条件,条件为true时才进行缓存。注解 @Cacheable()表示当有缓存则取缓存,没有则添加缓存,三个属性和注解@CachePut()属性一致。注解 @CacheEvict()表示当有缓存则清除缓存,除了前面说的三个属性外,还多出两个属性, allEntries为true清除所有元素;beforeInvocation为true时表示在调用该方法之前清除缓存中的指定元素,默认是方法成功执行之后触发,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。
SpringBoot 入门
接着添加RedisCacheConfig配置key生成规则、配置RedisTemplate和缓存序列化以json格式存储@Configuration@EnableCachingpublic 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核心代码@Overridepublic 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 ResponseResultlogin(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依赖配置application.yml, 这里用Docker添加的所以没有账号密码,当有账号密码时,url格式为:mongodb://user:pwd@ip:port/soaic?maxPoolSize=256; 当有多台数据库时,url格式为:mongodb://user:pwd@ip1:port1,ip2:port2/database?maxPoolSize=512spring: data: mongodb: uri: mongodb://localhost:27017/soaic?maxPoolSize=256 org.springframework.boot spring-boot-starter-data-mongodb
修改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 Collectionroles = 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 ListfindByUserName(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)@SpringBootTestpublic class MongoDBTests { @Autowired private MyUserRepository myUserRepository; @Autowired private MyUserDaoImpl myUserDaoImpl; @Test public void testSave() { MyUser myUser = new MyUser(); myUser.setUserName("Soaic"); myUser.setPassword("123456"); Collectionroles = 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查询
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 cleanpackage),最后就可以在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 .
编译完成后,可以通过下面命令查看及运行项目docker images
docker run -d --name hellospringboot -p 8443:8443 -p 8088:8088 hellospringboot
最后可以通过 和 这个两个地址访问了。彩蛋时间:免费分享Java技术资料,需要的可以私信我
SpringBoot 入门 来源:知乎作者:白天不懂夜的黑
原文:
转载地址:http://ndusi.baihongyu.com/