首页 文章 精选 留言 我的

精选列表

搜索[增删改查],共8405篇文章
优秀的个人博客,低调大师

SpringBoot-06:SpringBoot增删改一套完整的考试案例

------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 本此博客记录一套考试题,随后我把项目以及题目发到github上,简单的说一下springboot的开发 本此考试题用Spring+SpringMVC+MyBatis+SpringBoot+MySQL+Druid+.yml配置文件+thymeleaf模板引擎 我会把大量源码放上来,以及整合需要的注意点,大家可以一会去github上下载观看 项目概览: 一,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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>z05springbootmyself_exam</artifactId> <packaging>war</packaging> <name>z05springbootmyself_exam Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.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</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <!-- 核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 可以实现热部署,在IDEA上实现热部署还需一些额外的配置,请查阅资料 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>runtime</scope> </dependency> <!-- JDBC for mysql --> <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> <!-- mybatis --> <!--mybatis--> <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <!--fastJson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.12</version> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!--thymeleaf 新的模板引擎,比jsp要出色--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency> </dependencies> <build> <finalName>z05springbootmyself_exam</finalName> <!--maven插件--> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <!--xml配置,此是为了将来整合Hibernate或者mybatis 默认没有需要配置 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> </resources> </build> </project> pom.xml中的配置 注意点:如果自己工程不是通过官网骨架创建,自己粘的话,小心粘了俩个parent节点,他不会报错,但是在后续就不会下载jar包了 二,数据库脚本,我给一份 DROP TABLE IF EXISTS `air_quality_index`; CREATE TABLE `air_quality_index` ( `id` int(11) NOT NULL AUTO_INCREMENT, `district` varchar(255) NOT NULL, `monitorTime` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP, `pm10` int(255) NOT NULL, `pm25` int(255) NOT NULL, `monitoringStation` varchar(255) NOT NULL, `createDate` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of air_quality_index -- ---------------------------- INSERT INTO `air_quality_index` VALUES ('1', '西城区', '2018-05-22 09:34:05', '243', '176', '灵境胡同监测站', '2018-05-22 10:57:21'); INSERT INTO `air_quality_index` VALUES ('2', '东城区', '2018-05-22 09:34:05', '27', '33', '东四监测站', '2018-05-22 09:34:35'); INSERT INTO `air_quality_index` VALUES ('3', '海淀区', '2018-05-22 09:34:05', '21', '30', '航天桥监测站', '2018-05-22 09:34:35'); INSERT INTO `air_quality_index` VALUES ('4', '丰台区', '2018-05-22 09:34:05', '24333', '17', '七里庄监测站', '2018-05-22 10:55:20'); INSERT INTO `air_quality_index` VALUES ('5', '西城区', '2018-05-22 09:58:03', '100', '1', '北京某地', '2018-06-22 16:41:51'); INSERT INTO `air_quality_index` VALUES ('6', '东城区', '2018-05-22 09:58:03', '22', '22', '山东某地222333', '2018-05-22 10:54:36'); INSERT INTO `air_quality_index` VALUES ('7', '西城区', '2018-05-22 09:58:03', '22', '22', '天津某地', '2018-05-22 10:57:41'); INSERT INTO `air_quality_index` VALUES ('8', '西城区', '2018-05-22 09:58:03', '122', '232', '山东222', '2018-05-22 10:58:59'); INSERT INTO `air_quality_index` VALUES ('9', '0', '2018-05-22 09:58:03', '22', '22', '北京某地', '2018-05-22 11:26:42'); INSERT INTO `air_quality_index` VALUES ('10', '西城区', '2018-05-22 09:58:03', '22', '22', '天津某地', '2018-06-22 16:42:17'); INSERT INTO `air_quality_index` VALUES ('11', '西城区', '2018-05-22 09:58:03', '1', '22', '灵境胡同监测站', '2018-06-22 16:42:50'); 数据库脚本 三,application.yml的配置 server: #端口号 port: 8080 spring: #模板引擎 thymeleaf: prefix: classpath:/templates/ mode: HTML5 cache: false #阿里的druid datasource: name: test url: jdbc:mysql:///exam_air username: root password: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver filters: stat maxActive: 20 initialSize: 1 maxWait: 60000 minIdle: 1 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxOpenPreparedStatements: 20 mybatis: #映射的xml文件 mapper-locations: classpath:mapping/*.xml #别名 type-aliases-package: com.happy.entity application.yml配置 注意点:他是一种新的模板,yml不可以使用制表符TAB,它通过空格表示层级关系,同样的节点反复出现会有问题 我在里面配置了tomcat的端口,druid的数据源,以及mybatis的部分配置 四,项目骨架预览 五,thymeleaf模板的创建,他是以.html后缀名结尾的文件 1.主页面: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> <!--引入js的时候切记 不需要加 springboot的默认文件名称--> <script type="text/javascript" th:src="@{/js/jquery-1.8.3.min.js}"></script> <script type="text/javascript" th:src="@{/js/jquery.pagination.js}"></script> <script type="text/javascript" th:src="@{/js/bootstrap-modal.js}"></script> <script type="text/javascript"> window.onload=function () { $("tr:odd").css("background","pink"); } </script> <title>Title</title> <style type="text/css"> table{ border-collapse: collapse; } </style> </head> <body> <table width="70%" border="1" align="center" id="list"> <caption><h1 style="height: 50px;line-height5:0px;border: 1px">空气质量检测信息库</h1> 按区域查询 <select name="district" id="ourSelect"> <option value="0">不限</option> <option value="西城区">西城区</option> <option value="东城区">东城区</option> <option value="海淀区">海淀区</option> <option value="丰台区">丰台区</option> </select> <input type="button" onclick="myselect()" value="查找"/> <a href="/goAddPage">添加空气质量信息</a> </caption> <thead> <tr class="t_head"> <th>序号</th> <th>区域</th> <th>检测时间</th> <th>PM10数据</th> <th>PM2.5数据局</th> <th>监测站</th> </tr> </thead> <tbody id="list-content"> </tbody> </table> <div class="pagination" id="pagination"></div> <div id="isOK"></div> <script type="text/javascript"> $(function () { $("#update").hide(); }) load(); //默认初始化 /*点击查询的触发事件*/ function load() { $.ajax({ url: "/findAll", type: "post", success: function (data) { //清空数据 $("#list-content").html(''); //追加数据 $.each(data, function (i, dom) { //一个dom就是一个新闻对象 $("#list-content").append("<tr><td>"+ dom.id + "</td><td><a onclick='update("+dom.id+")'>"+dom.district+"</a>" + "</td><td>" + dom.monitorTime + "</td><td>" + dom.pm10 + "</td><td >" + dom.pm25 + "</td><td>"+ dom.monitoringStation+"</td></tr>"); }); $("tr:odd").css("background","pink"); } }); }; function myselect() { $.ajax({ url: "/selectByCondition", type: "post", data:{"district":$("#ourSelect").val()}, success: function (data) { //清空数据 $("#list-content").html(''); //追加数据 $.each(data, function (i, dom) { //一个dom就是一个新闻对象 $("#list-content").append("<tr><td>"+ dom.id + "</td><td><a onclick='update("+dom.id+")'>"+dom.district+"</a>" + "</td><td>" + dom.monitorTime + "</td><td>" + dom.pm10 + "</td><td >" + dom.pm25 + "</td><td>"+ dom.monitoringStation+"</td></tr>"); }); $("tr:odd").css("background","pink"); } }); }; function update(id) { $("#update").show(); $("#list").hide(); $.ajax({ url:'/goUpdatePage', type:"post", data:{"id":id}, success:function(data){ $("[name=id]").val(data.id); $("[name=district]").val(data.district); $("[name=monitorTime]").val(data.monitorTime); $("[name=pm10]").val(data.pm10); $("[name=pm25]").val(data.pm25); $("[name=monitoringStation]").val(data.monitoringStation); } }); } </script> <center> <div id="update"> <h2>空气质量信息维护页面</h2> <form method="post" action="/updateAir"> <input type="hidden" name="id"/> <p>监测区域: <select name="district"> <option value="0">不限</option> <option value="西城区">西城区</option> <option value="东城区">东城区</option> <option value="海淀区">海淀区</option> <option value="丰台区">丰台区</option> </select></p> <p>监测日期: <input type="text" name="monitorTime" /></p> <p>PM10值: <input type="text" name="pm10" /></p> <p>PM2.5值: <input type="text" name="pm25" /></p> <p>监测站: <input type="text" name="monitoringStation"/></p> <input type="submit" value="更新"/> <input type="button" onclick="javascript:$('#list').show();$('#update').hide()" value="返回"/> </form> </div> </center> </body> </html> main.html 2.添加页面: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" th:src="@{/js/jquery-1.8.3.min.js}"></script> <style type="text/css"> #box{ width:30%; margin: 0px auto; } </style> <script type="text/javascript"> function checkInfo() { var district=$("[name=district]").val(); var monitorTime=$("[name=monitorTime]").val(); var pm10temp=$("[name=pm10]").val(); var pm10=parseInt(pm10temp); var pm25temp=$("[name=pm25]").val(); var pm25=parseInt(pm25temp); var monitoringStation=$("[name=monitoringStation]").val(); if(monitorTime==""){ alert("检测时间不能为空"); return false; } if(pm10temp==""){ alert("pm10的值不能为空"); return false; } var reg=/^\d+$/; if(!reg.test(pm10)){ alert('pm10只能为正整数'); return false; } if(pm25temp==""){ alert("pm25不能为空"); return false; } if(monitoringStation==""){ alert("监测站不能为空"); return false; } if(district=="不限"){ alert("监测区域不能选择不限"); return false; } return true; } $(function () { $("#form1").submit(function () { return checkInfo(); }); }); </script> </head> <body> <div id="box"> <h2>添加空气质量信息</h2> <form id="form1" method="post" action="/addAir"> <p>监测区域: <select name="district"> <option value="不限">不限</option> <option value="西城区">西城区</option> <option value="东城区">东城区</option> <option value="海淀区">海淀区</option> <option value="丰台区">丰台区</option> </select></p> <p>监测日期: <input type="text"name="monitorTime"/></p> <p>PM10值: <input type="text"name="pm10"/></p> <p>PM2.5值: <input type="text"name="pm25"/></p> <p>监测站: <input type="text"name="monitoringStation"/></p> <input type="submit" value="提交"/> </form> </div> <script type="text/javascript"> </script> </body> </html> addAir.html 3.修改页面: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" th:src="@{/js/jquery-1.8.3.min.js}"></script> <style type="text/css"> #box{ width:30%; margin: 0px auto; } </style> <script type="text/javascript"> </script> </head> <body> <div id="box"> <h2>空气质量信息维护页面</h2> <form method="post" action="/updateAir"> <input type="hidden" name="id" value="${air.id}"/> <p>监测区域: <select name="district"> <option value="0">不限</option> <option value="西城区">西城区</option> <option value="东城区">东城区</option> <option value="海淀区">海淀区</option> <option value="丰台区">丰台区</option> </select></p> <p>监测日期: <input type="text"value="${air.monitorTime}"/></p> <p>PM10值: <input type="text"value="${air.pm10}"/></p> <p>PM2.5值: <input type="text"value="${air.pm25}"/></p> <p>监测站: <input type="text"value="${air.monitoringStation}"/></p> <input type="submit" value="更新"/> <input type="button" value="返回"/> </form> </div> <script type="text/javascript"> </script> </body> </html> updateAir.html 注意点:他是一种新的模板,与jsp写法有不同,展示数据的方式,引用js,css都不太一样,也不同与EL,JSTL,需要摸索 它的核心th <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> 六,实体类 package cn.happy.entity; import cn.happy.util.JsonDateSerializer; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; public class AirModel { private Integer id; private String district; @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") @JsonSerialize(using = JsonDateSerializer.class) /*处理日期格式*/ private Date monitorTime; private Integer pm10; private Integer pm25; private String monitoringStation; private Date createDate; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getDistrict() { return district; } public void setDistrict(String district) { this.district = district; } public Date getMonitorTime() { return monitorTime; } public void setMonitorTime(Date monitorTime) { this.monitorTime = monitorTime; } public Integer getPm10() { return pm10; } public void setPm10(Integer pm10) { this.pm10 = pm10; } public Integer getPm25() { return pm25; } public void setPm25(Integer pm25) { this.pm25 = pm25; } public String getMonitoringStation() { return monitoringStation; } public void setMonitoringStation(String monitoringStation) { this.monitoringStation = monitoringStation; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } } AirModel 注意点:日期类型处理格式 @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") @JsonSerialize(using = JsonDateSerializer.class) /*处理日期格式*/ private Date monitorTime; 七,工具类---就是处理日期格式的那个工具类 package cn.happy.util; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class JsonDateSerializer extends JsonSerializer<Date> { private SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException { String value = dateFormat.format(date); gen.writeString(value); } } JsonDateSerializer 八,Dao层 1.接口: package cn.happy.mapper; import cn.happy.entity.AirModel; import java.util.List; import java.util.Map; public interface IAirDAO { //查询所有 public List<AirModel> findAll() throws Exception; //添加 public int addAir(AirModel model) throws Exception; //修改 public int updateAir(AirModel model) throws Exception; //按条件查询 public List<AirModel> findAirByCondition(Map<String,Object> map) throws Exception; //根据编号查询对象的方法 public AirModel selectOneById(Integer id) throws Exception; } dao层接口 注意:他是一个普通的接口,没有注解是因为我在别处配置了一道,在yml有配置,在启动的那个main方法上面也有一道配置 2.dao层的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="cn.happy.mapper.IAirDAO" > <!--01.查询所有记录--> <select id="findAll" resultType="cn.happy.entity.AirModel"> select * from air_quality_index </select> <insert id="addAir"> insert into air_quality_index(district,monitorTime,pm10,pm25,monitoringStation,createDate) values(#{district},#{monitorTime},#{pm10},#{pm25},#{monitoringStation},now()) </insert> <!--修改--> <update id="updateAir"> update air_quality_index set district=#{district},monitorTime=#{monitorTime},pm10=#{pm10},pm25=#{pm25},monitoringStation=#{monitoringStation},createDate=now() where id=#{id} </update> <!--按条件查询--> <select id="findAirByCondition" resultType="cn.happy.entity.AirModel"> select * from air_quality_index <where> <if test='district!="0"'> district=#{district} </if> </where> </select> <!--查询单项--> <select id="selectOneById" resultType="cn.happy.entity.AirModel"> select * from air_quality_index where id=#{id} </select> </mapper> dao的映射xml 注意:命名空间,别名 还有那个智能标签if test里面的写法 直接传的String类型他会报错,说没有getString的方法,所以用map往里传参数 假如test='address!=0',他会报错,说不可以转换为数字,故写为test='address!="0"'即可 九,service层: 1.接口: package cn.happy.service; import cn.happy.entity.AirModel; import java.util.List; public interface IAirService { //查询所有 public List<AirModel> findAll() throws Exception; //添加 public int addAir(AirModel model) throws Exception; //修改 public int updateAir(AirModel model) throws Exception; //按条件查询 public List<AirModel> findAirByCondition(String district) throws Exception; //根据编号查询对象的方法 public AirModel selectOneById(Integer id) throws Exception; } IAirService 2.实现类: package cn.happy.service.impl; import cn.happy.entity.AirModel; import cn.happy.mapper.IAirDAO; import cn.happy.service.IAirService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.HashMap; import java.util.List; import java.util.Map; @Service("airService") public class AirServiceImpl implements IAirService { @Resource(name = "IAirDAO") private IAirDAO dao; @Override public List<AirModel> findAll() throws Exception { return dao.findAll(); } @Override @Transactional public int addAir(AirModel model) { try { return dao.addAir(model); } catch (Exception e) { e.printStackTrace(); return 0; } } @Override public int updateAir(AirModel model) throws Exception { return dao.updateAir(model); } @Override public List<AirModel> findAirByCondition(String district) throws Exception { Map<String,Object> map=new HashMap<String,Object>(); map.put("district",district); return dao.findAirByCondition(map); } @Override public AirModel selectOneById(Integer id) throws Exception { return dao.selectOneById(id); } } AirServiceImpl 注意点:和普通的SSM注解开发没有什么太大的区别,值得注意的是事务的运用,需要在main那边开启一道配置 十,controller层: package cn.happy.controller; import cn.happy.entity.AirModel; import cn.happy.service.IAirService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.validation.constraints.FutureOrPresent; import java.util.List; @Controller public class FirstController { //植入Service @Resource(name = "airService") private IAirService myAirService; @RequestMapping("/goHome") public String getHome(){ return "main"; } @RequestMapping("/findAll") @ResponseBody public Object findAll() throws Exception { List<AirModel> list = myAirService.findAll(); return list; } @RequestMapping("/goAddPage") public String goAddPage(){ return "addAir"; } //添加数据 @RequestMapping("/addAir") public String addAir(AirModel model, Model modelData) throws Exception { int count = myAirService.addAir(model); if (count>0){ return "redirect:goHome"; }else{ modelData.addAttribute("erroMsg","添加失败"); return "redirect:goHome"; } } @RequestMapping("/goUpdatePage") @ResponseBody public Object goUpdatePage(int id) throws Exception { AirModel air = myAirService.selectOneById(id); return air; } @RequestMapping("/updateAir") public String updateAir(AirModel model) throws Exception { int count = myAirService.updateAir(model); return "redirect:goHome"; } //按条件查询 @RequestMapping("selectByCondition") @ResponseBody public Object selectByCondition(String district) throws Exception { List<AirModel> list = myAirService.findAirByCondition(district); return list; } } FirstController 普普通通,做过SSM的发现,没什么不同 十一,SpringBoot核心启动类的配置 package cn.happy; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication //扫描mapper映射 @MapperScan("cn.happy.*") /*开启事务*/ @EnableTransactionManagement public class ExamApplication { public static void main(String[] args) { SpringApplication.run(ExamApplication.class, args); } } 注意点:开启事务和mybatis的扫描 十二,可以启动项目和调试了 注意: 我没仔细讲js,css,logback,logback就普通配置,无关紧要,css,js放在static下面即可,引入方式可以参考案例,我引用了一个bootstrap的js,这无关紧要,不影响,只需要jquery即可 作者:晨曦Dawn 转载请注明出处,地址:https://www.cnblogs.com/DawnCHENXI/p/9221212.html github地址:(一会上传完成之后,发出来) 如果上方博客有错误或者疑惑,请指出,感激不敬!!!!!!!!!!!!!!

优秀的个人博客,低调大师

Python全栈 MySQL 数据库 (表字段、删、改、、函数)

ParisGabriel 每天坚持手写一天一篇 决定坚持几年 为了梦想为了信仰 开局一张图 查询SQL变量show variables 1.表字段的操作 1.语法:alter table表名 执行动作; 2.添加字段(add) alter table表名add字段名 数据类型;(尾插) alter table 表名 add 字段名 数据类型first;(头插) alter table 表名 add 字段名 数据类型after字段名;(指定插入) 3.删除字段(drop) alter table 表名drop字段名; 4.修改数据类型(modify) alter table 表名modify字段名 新数据类型; 5.重命名(rename) alter table 表名rename表名;2.字符类型 1.字符类型宽度与数值类型宽度的区别 1.数值类型宽度为显示宽度,只用于select查询显示 占用储存无关,可用zerofill查看效果 2.枚举类型 1.单选(enum):字段名enum(值1,值2...); 2.多选(set):字段名set(值1,值2...); (多项放在一个字符串内用,号隔开) 3.日期时间类型 1.date:“YYYY-MM-DD” 2.time:“HH:MM:SS” 3.datetime:“YYYY-MM-DD HH:MM:SS” 4.timestamp:“YYYY-MM-DD HH:MM:SS” 5.datetime:不给值默认返回Null 6.timestamp:不给值默认返回系统时间 3. 日期时间函数 1.now()返回服务器当前的时间 2.curdate()返回当前时期 3.curtime()返回当前日期 4.year(date)返回指定时间的年份 5.date(date)返回指定时间的日期 6.time(date)返回指定时间的时间4.日期时间运算 1.语法格式 select * from 表名 where 字段名 运算符(时间-interval时间间隔单位); 时间间隔单位: 1 day | 2hour | 1 minute | 2year | month 5.表记录管理 1.删除表记录 1.deletefrom 表名 where 条件; 注意: 如果不加where条件,所有记录全部清空 2.更改表记录 1.update表名 set 字段1=值1,字段名2=值2,... where 条件 注意: 如果不加where条件,所有记录全部更改 3.运算符操作 1.数值比较/字符比较 1.数值比较: = != > >= < <= 2.字符比较: = != 2.逻辑比较 1.and 2.or 3.范围内比较 1.where 字段名between值1 and 值2 2.where 字段名in(值1,值2,....) 3.where 字段名not in(值1,值2,...) 4.匹配空、非空 1.空:where nameisnull 2.非空:where nameis notnull 3.注意 1.NILL:空值,只能用is或is not取匹配 2.“ ”:空字符串,用=或!=去匹配 4.模糊比较 1.where 字段名like表达式 2.表达式 1._:匹配单个字符 2.% :匹配0到多个字符 NULL不会被统计6.SQL查询: 1语法顺序: 3.select... 聚合函数from表名 1.where 2.group by... 4.having... 5.order by... 6.limit...; 2.order by 1.给出查询结果进行排序 2...order by字段名 升序/降序 升序:ASC(默认排序方式) 降序:DESC 3.limit(永远放在SQL语句的最后) 1.作用:显示查询记录的个数 2.用法 limitn 显示n条记录 limit m,n m表示 从m+1条记录开始显示显示n条记录 limit 2,3 显示第3,4,5条记录 3.分页 每页显示5条记录,显示第4页内容 第1页:limit 0,5 #1,2,3,4,5 第2页:limit 5,5 第3页:limit 10,5 第4页:limit 15,5 每页显示n条记录,显示第m页: limit(m-1)*n,n4.聚合函数 avg(字段名):求该字段的平均值 sum(字段名):求和 max(字段名):最大值 min(字段名):最小值 count(字段名):统计该字段的个数 create database MOSHOU; use MOSHOU; create table hero( id int, name char(15), sex enum("男","女"), country char(10) )default charset=utf8; insert into hero values (1,"曹操","男","魏国"), (2,"小乔","女","吴国"), (3,"诸葛亮","男","蜀国"), (4,"貂蝉","女","东汉"), (5,"赵子龙","男","蜀国"), (6,"魏延","男","蜀国"); use MOSHOU; create table sanguo( id int, name char(20), gongji int, fangyu tinyint unsigned, sex enum("男","女"), country varchar(20) )default charset=utf8; insert into sanguo values (1,'诸葛亮',120,20,'男','蜀国'), (2,'司马懿',119,25,'男','魏国'), (3,'关羽',188,60,'男','蜀国'), (4,'赵云',200,66,'男','魏国'), (5,'孙权',110,20,'男','吴国'), (6,'貂蝉',666,10,'女','魏国'), (7,null,1000,99,'男','蜀国'), (8,'',1005,88,'女','蜀国'); 1、创建库 studb2 2、在库中创建表 t1 ,字段有3个:name、age、phnumber 3、查看表结构 4、在表中第一列添加一个 id 字段 5、把 phnumber 的数据类型改为 bigint 6、在表中最后一列添加一个字段 address 7、删除表中的 age 字段 8、查看表结构 答案: use studb2; create table t1( name char(20), age tinyint unsigned, phnumber char(11) ); desc t1; alter table t1 add id int first; alter table t1 modify phnumber bigint; alter table t1 add address varchar(50); alter table t1 drop age; desc t1; 1、在表中插入3条记录 2、查找2018年7月2日有哪些用户充值了 3、查找2018年7月份充值的信息 4、查找7月30日10:00-12:00充值的信息 答案: insert into t7 values (3,"小昭",19000520,3000,20180630000000), (4,"赵敏",19000521,4000,20180702000000), (5,"周芷若",19010522,3500,20180702100000); select * from t7 where date(shijian)="2018-07-02"; select * from t7 where date(shijian)>="2018-07-01" and date(shijian)<="2018-07-31"; select * from t7 where date(shijian)="2018-07-31" and time(shijian)>="10:00:00" and time(shijian)<="12:00:00"; 1、查询1天以内的记录 2、查询1年以前的记录 3、查询1天以前,3天以内的记录 答案: select * from t7 where shijian > (now()-interval 1 day); select * from t7 where shijian < (now()-interval 1 year); select * from t7 where shijian < (now()-interval 1 day) and shijian > (now()-interval 3 day); 1、查找所有蜀国人的信息 2、查找所有女英雄的姓名、性别和国家 3、把id为2的记录改为典韦,性别男,国家魏国 4、删除所有蜀国英雄 5、把貂蝉的国籍改为魏国 6、删除所有表记录 答案: select * from hero where country="蜀国"; select name,sex,country from hero where sex="女"; update hero set name="典韦",sex="男",country="魏国" where id=2; delete from hero where country="蜀国"; update hero set country="魏国" where name="貂蝉"; delete from hero; 1、找出攻击值高于200的蜀国英雄的名字、攻击力 2、将吴国英雄中攻击值为110的英雄的攻击值改为100,防御力改为60 3、查找蜀国和魏国的英雄信息 答案: select name as n,gongji as g from sanguo where gongji>200 and country="蜀国"; update sanguo set gongji=100,fangyu=60 where country="吴国" and gongji=110; select * from sanguo where country="蜀国" or country="魏国"; 1、查找攻击值100-200的蜀国英雄信息 2、找到蜀国和吴国以外的国家的女英雄信息 3、找到id为1、3或5的蜀国英雄 和 貂蝉的信息 答案: select * from sanguo where gongji between 100 and 200 and country="蜀国"; select * from sanguo where country not in("蜀国","吴国") and sex="女"; select * from sanguo where (id in(1,3,5) and country="蜀国") or name="貂蝉"; 1、在蜀国英雄中,查找防御值倒数第二名至倒数第四名的英雄的记录 2、在蜀国英雄中,查找攻击值前3名且名字不为 NULL 的英雄的姓名、攻击值和国家 答案: select * from sanguo where country="蜀国" order by fangyu asc limit 1,3; select name,gongji,country from sanguo where country="蜀国" and name is not NULL order by gongji DESC limit 3; 1、攻击力最强值是多少 2、统计id 、name 两个字段分别有几条记录 ## 空值 NULL 不会被统计,""会被统计 3、计算蜀国英雄的总攻击力 4、统计蜀国英雄中攻击值大于200的英雄的数量 答案: select max(gongji) from MOSHOU.sanguo; select count(id),count(name) from sanguo; select sum(gongji) from MOSHOU.sanguo where country="蜀国"; select count(*) from MOSHOU.sanguo where gongji>200 and country="蜀国";

优秀的个人博客,低调大师

Elasticsearch如何是如何实现分布式增删改,一文搞懂

分布式文档存储 路由文档到分片 主分片和复制分片如何交互 新建、索引和删除文档 检索文档 局部更新文档 多文档模式 福利 路由文档到分片 当你索引一个文档,它被存储在单独一个主分片上。Elasticsearch是如何知道文档属于哪个分片的呢?当你创建一个新文档,它是如何知道是应该存储在分片1还是分片2上的呢?进程不能是随机的,因为我们将来要检索文档。事实上,它根据一个简单的算法决定: shard = hash(routing) % number_of_primary_shards routing 值是一个任意字符串,它默认是 _id 但也可以自定义。这个 routing 字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是 0 到 number_of_primary_shards - 1 ,这个数字就是特定文档所在的分片。 这也解释了为什么主分片的数量只能在创建索引时定义且不能修改:如果主分片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。 所有的文档API( get 、 index 、 delete 、 bulk 、 update 、 mget )都接收一个 routing 参数,它用来自定义文档到分片的映射。自定义路由值可以确保所有相关文档——例如属于同一个人的文档——被保存在同一分片上。 主分片和复制分片如何交互 为了阐述意图,我们假设有三个节点的集群。它包含一个叫做 bblogs 的索引并拥有两个主分片。每个主分片有两个复制分片。相同的分片不会放在同一个节点上,所以我们的集群是这样的: 我们能够发送请求给集群中任意一个节点。每个节点都有能力处理任意请求。每个节点都知道任意文档所在的节点,所以也可以将请求转发到需要的节点。下面的例子中,我们将发送所有请求给 Node 1 ,这个节点我们将会称之为请求节点(requesting node)。 提示: 当我们发送请求,最好的做法是循环通过所有节点请求,这样可以平衡负载。 新建、索引和删除文档 新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。 下面我们罗列在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤: 客户端给 Node 1 发送新建、索引或删除请求。 节点使用文档的 _id 确定文档属于分片 0 。它转发请求到 Node 3 ,分片 0 位于这个节点上。 Node 3 在主分片上执行请求,如果成功,它转发请求到相应的位于 Node 1 和 Node 2 的复制节点上。当所有的复制节点报告成功, Node 3 报告成功到请求的节点,请求的节点再报告给客户端。 客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片。你的修改生效了。 有很多可选的请求参数允许你更改这一过程。你可能想牺牲一些安全来提高性能。这一选项很少使用因为Elasticsearch已经足够快,不过为了内容的完整我们将做一些阐述。 replication 复制默认的值是 sync 。这将导致主分片得到复制分片的成功响应后才返回。 如果你设置 replication 为 async ,请求在主分片上被执行后就会返回给客户端。它依旧会转发请求给复制节点,但你将不知道复制节点成功与否。 上面的这个选项不建议使用。默认的 sync 复制允许Elasticsearch强制反馈传输。 async 复制可能会因为在不等待其它分片就绪的情况下发送过多的请求而使Elasticsearch过载。 consistency 默认主分片在尝试写入时需要规定数量(quorum)或过半的分片(可以是主节点或复制节点)可用。这是防止数据被写入到错的网络分区。规定的数量计算公式如下: int( (primary + number_of_replicas) / 2 ) + 1 consistency 允许的值为 one (只有一个主分片), all (所有主分片和复制分片)或者默认的 quorum 或过半分片。 注意 number_of_replicas 是在索引中的的设置,用来定义复制分片的数量,而不是现在活动的复制节点的数量。如果你定义了索引有3个复制节点,那规定数量是: int( (primary + 3 replicas) / 2 ) + 1 = 3 但如果你只有2个节点,那你的活动分片不够规定数量,也就不能索引或删除任何文档。 timeout 当分片副本不足时会怎样?Elasticsearch会等待更多的分片出现。默认等待一分钟。如果需要,你可以设置 timeout 参数让它终止的更早: 100 表示100毫秒, 30s 表示30秒。 注意: 新索引默认有 1 个复制分片,这意味着为了满足 quorum 的要求需要两个活动的分片。当然,这个默认设置将阻止我们在单一节点集群中进行操作。为了避开这个问题,规定数量只有在 number_of_replicas 大于一时才生效。 检索文档 文档能够从主分片或任意一个复制分片被检索。 下面我们罗列在主分片或复制分片上检索一个文档必要的顺序步骤: 客户端给 Node 1 发送get请求。 节点使用文档的 _id 确定文档属于分片 0 。分片 0 对应的复制分片在三个节点上都有。此时,它转发请求到 Node 2 。 Node 2 返回endangered给 Node 1 然后返回给客户端。 对于读请求,为了平衡负载,请求节点会为每个请求选择不同的分片——它会循环所有分片副本。 可能的情况是,一个被索引的文档已经存在于主分片上却还没来得及同步到复制分片上。这时复制分片会报告文档未找到,主分片会成功返回文档。一旦索引请求成功返回给用户,文档则在主分片和复制分片都是可用的。 局部更新文档 update API 结合了之前提到的读和写的模式。 下面我们罗列执行局部更新必要的顺序步骤: 客户端给 Node 1 发送更新请求。 它转发请求到主分片所在节点 Node 3 。 Node 3 从主分片检索出文档,修改 _source 字段的JSON,然后在主分片上重建索引。如果有其他进程修改了文档,它以retry_on_conflict 设置的次数重复步骤3,都未成功则放弃。 如果 Node 3 成功更新文档,它同时转发文档的新版本到 Node 1 和 Node 2 上的复制节点以重建索引。当所有复制节点报告成功, Node 3 返回成功给请求节点,然后返回给客户端。 基于文档的复制 当主分片转发更改给复制分片时,并不是转发更新请求,而是转发整个文档的新版本。记住这些修改转发到复制节点是异步的,它们并不能保证到达的顺序与发送相同。如果Elasticsearch转发的仅仅是修改请求,修改的顺序可能是错误的,那得到的就是个损坏的文档。 多文档模式 mget 和 bulk API与单独的文档类似。差别是请求节点知道每个文档所在的分片。它把多文档请求拆成每个分片的对文档请求,然后转发每个参与的节点。 一旦接收到每个节点的应答,然后整理这些响应组合为一个单独的响应,最后返回给客户端。 下面我们将罗列通过一个 mget 请求检索多个文档的顺序步骤: 客户端向 Node 1 发送 mget 请求。 Node 1 为每个分片构建一个多条数据检索请求,然后转发到这些请求所需的主分片或复制分片上。当所有回复被接收, Node 1 构建响应并返回给客户端。routing 参数可以被 docs 中的每个文档设置。 下面我们将罗列使用一个 bulk 执行多个 create 、 index 、 delete 和 update 请求的顺序步骤: 3. 客户端向 Node 1 发送 bulk 请求。 4. Node 1 为每个分片构建批量请求,然后转发到这些请求所需的主分片上。 5. 主分片一个接一个的按序执行操作。当一个操作执行完,主分片转发新文档(或者删除部分)给对应的复制节点,然后执行下一个操作。复制节点为报告所有操作完成,节点报告给请求节点,请求节点整理响应并返回给客户端。 bulk API还可以在最上层使用 replication 和 consistency 参数, routing 参数则在每个请求的元数据中使用。 为什么是奇怪的格式? 当我们在《批量》一章中学习了批量请求后,你可能会问:“为什么 bulk API需要带换行符的奇怪格式,而不是像 mget API一样使用JSON数组?” 为了回答这个问题,我们需要简单的介绍一下背景: 批量中每个引用的文档属于不同的主分片,每个分片可能被分布于集群中的某个节点上。这意味着批量中的每个操作(action)需要被转发到对应的分片和节点上。 如果每个单独的请求被包装到JSON数组中,那意味着我们需要: 解析JSON为数组(包括文档数据,可能非常大) 检查每个请求决定应该到哪个分片上 为每个分片创建一个请求的数组 序列化这些数组为内部传输格式 发送请求到每个分片 这可行,但需要大量的RAM来承载本质上相同的数据,还要创建更多的数据结构使得JVM花更多的时间执行垃圾回收。 取而代之的,Elasticsearch则是从网络缓冲区中一行一行的直接读取数据。它使用换行符识别和解析action/metadata行,以决定哪些分片来处理这个请求。 这些行请求直接转发到对应的分片上。这些没有冗余复制,没有多余的数据结构。整个请求过程使用最小的内存在进行。 粉丝福利 微信搜一搜「 码上代码」回复【面试资料】,【技术书籍】有我准备的一线大厂面试资料和简历模板和java必看技术书籍 大家好,感谢各位人才 能看到这里的都是您已是佼佼者 我会持续为大家做技术分享 预知下篇如何 请点赞、收藏和评论,我们下期见! 本文同步分享在 博客“码上代码”(CSDN)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

asp.net core webapi 使用ef 对mysql进行增删改,并生成Docker镜像构建容器运行

1.构建运行mysql容器,添加数据库user 参考Docker创建运行多个mysql容器,地址http://www.cnblogs.com/heyangyi/p/9288402.html 添加user数据库,添加tbusers表 2.创建asp.net core webapi 应用程序 参考Docker 为 ASP.NET Core WebApi 应用程序生成 Docker 映像,创建容器并运行,地址http://www.cnblogs.com/heyangyi/p/9323407.html <2.1> 修改appsettings.json 文件,添加 dbconn 数据库链接配置 <2.2> 新增Config类,用来存储配置 public class Config { public static string dbconn; } <2.3> 修改Program 类,读取配置 public class Program { private static IConfigurationRoot Configuration { get; set; } public static void Main(string[] args) { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); Configuration = builder.Build(); Config.dbconn = Configuration.GetValue<string>("dbconn"); BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); } <2.4> 新增DataContext类 安装引用:MySql.Data.EntityFrameworkCore public class DataContext : DbContext { public DbSet<tbuser> tbusers { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseMySQL(Config.dbconn); } public class tbuser { [DatabaseGenerated(DatabaseGeneratedOption.None)] public int id { get; set; } public string nickName { get; set; } public string email { get; set; } } <2.5> 新增tbuserDataHandle类 public class tbuserDataHandle : DataContext { public static int adduser(tbuser user) { try { using (var context = new DataContext()) { context.Database.EnsureCreated(); context.Add(user); context.SaveChanges(); } return 200; } catch (Exception ex) { return 300; } } public static List<tbuser> Get() { try { using (var context = new DataContext()) { var users = context.tbusers; List<tbuser> items = new List<tbuser>(); foreach (var item in users) { items.Add(item); } return items; } } catch (Exception ex) { return null; } } public static tbuser Get(int id) { try { using (var context = new DataContext()) { var u = context.tbusers.Find(id); return u; } } catch (Exception ex) { return null; } } public static int Delete(int id) { try { using (var context = new DataContext()) { var u = context.tbusers.Remove(new tbuser() { id = id }); context.SaveChanges(); return 200; } } catch (Exception ex) { return 300; } } public static int Put(int id, tbuser user) { try { using (var context = new DataContext()) { var u = context.tbusers.Update(user); context.SaveChanges(); return 200; } } catch (Exception ex) { return 300; } } } <2.6> 新增userController Api [Produces("application/json")] [Route("api/user")] public class userController : Controller { // POST api/user [HttpPost] public int Post(tbuser user) { return tbuserDataHandle.adduser(user); } // GET api/user [HttpGet] public List<tbuser> Get() { return tbuserDataHandle.Get(); } // GET api/user/5 [HttpGet("{id}")] public tbuser Get(int id) { return tbuserDataHandle.Get(id); } // DELETE api/user/5 [HttpDelete("{id}")] public int Delete(int id) { return tbuserDataHandle.Delete(id); } // PUT api/user/5 [HttpPut("{id}")] public int Put(int id, tbuser user) { return tbuserDataHandle.Put(id, user); } } 3.生成项目,构建docker镜像并创作容器运行 修改docker-compose.yml ,docker-compose.override.yml 的 version 为: version: '2.0' 配置 appsettings.json 的 dbconn 为: "dbconn": "server=192.168.99.100;user id=root;password=123456;persistsecurityinfo=True;port=3307;database=user;SslMode=none" 进入到E:\web\ilinkcore (这个目录为解决方案的根目录) docker-compose up 执行成功后创建一个 ilinkcore 的镜像,并且运行了一个 ilinkcore_ilinkcore_1的容器,将本机的32783端口映射到容器的80端口 4.测试访问接口 添加反向代理,修改nginx 配置 server{ listen 84; server_name localhost; location / { proxy_pass http://192.168.99.100:32783; index index.html index.htm; } } 重新运行nginx,使用Postman进行api接口测试 <4.1> 测试接口进行添加user数据 Headers 中添加项: Content-Type:application/json <4.2>查看所有用户数据 <4.3> 检索某个用户数据 <4.4> 更新某个用户数据 Headers 中添加项: Content-Type:application/json <4.5> 删除单个用户 博客内容仅代表个人观点,如发现阐述有误,麻烦指正,谢谢!

优秀的个人博客,低调大师

【熵教育】探索SpringBoot中的SpringMVC——熵学院

spring boot就是一个大框架里面包含了许许多多的东西,其中spring就是最核心的内容之一,当然就包含spring mvc。spring mvc 是只是spring 处理web层请求的一个模块。因此他们的关系大概就是这样:spring mvc < spring <springboot。 理清SpringBoot与SpringMVC的关系 Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。 Spring MVC是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种XML、 JavaConfig、hin处理起来比较繁琐。 于是为了简化开发者的使用,从而创造性地推出了Spring boot,约定优于配置,简化了spring的配置流程。 说得更简便一些:Spring 最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件。 大家觉得挺好用,于是按照这种模式搞了一个 MVC框架(一些用Spring 解耦的组件),用开发 web 应用( SpringMVC )。 然后有发现每次开发都写很多样板代码,为了简化工作流程,于是开发出了一些“懒人整合包”(starter),这套就是 Spring Boot。 Spring MVC的功能Spring MVC提供了一种轻度耦合的方式来开发web应用。Spring MVC是Spring的一个模块,式一个web框架。 通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。 Spring Boot的功能Spring Boot实现了自动配置,降低了项目搭建的复杂度。 众所周知Spring框架需要进行大量的配置,Spring Boot引入自动配置的概念,让项目设置变得很容易。 Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。 同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box),大部分的Spring Boot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。Spring Boot只是承载者,辅助你简化项目搭建过程的。如果承载的是WEB项目,使用Spring MVC作为MVC框架,那么工作流程和你上面描述的是完全一样的,因为这部分工作是Spring MVC做的而不是Spring Boot。对使用者来说,换用Spring Boot以后,项目初始化方法变了,配置文件变了,另外就是不需要单独安装Tomcat这类容器服务器了,maven打出jar包直接跑起来就是个网站,但你最核心的业务逻辑实现与业务流程实现没有任何变化。 所以,用最简练的语言概括就是: Spring 是一个“引擎”; Spring MVC 是基于Spring的一个 MVC 框架 ; Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。 Spring MVC自动配置 Spring Boot为Spring MVC提供的auto-configuration适用于大多数应用,并在Spring默认功能上添加了以下特性: 引入ContentNegotiatingViewResolver和BeanNameViewResolverbeans。 对静态资源的支持,包括对WebJars的支持。 自动注册Converter,GenericConverter,Formatterbeans。 对HttpMessageConverters的支持。 自动注册MessageCodeResolver。 对静态index.html的支持。 对自定义Favicon的支持。 自动使用ConfigurableWebBindingInitializerbean。 如果保留Spring Boot MVC特性,你只需添加其他的MVC配置(拦截器,格式化处理器,视图控制器等)。你可以添加自己的WebMvcConfigurerAdapter类型的@Configuration类,而不需要注解@EnableWebMvc。如果希望使用自定义的RequestMappingHandlerMapping,RequestMappingHandlerAdapter,或ExceptionHandlerExceptionResolver,你可以声明一个WebMvcRegistrationsAdapter实例提供这些组件。 如果想全面控制Spring MVC,你可以添加自己的@Configuration,并使用@EnableWebMvc注解。 HttpMessageConverters Spring MVC使用HttpMessageConverter接口转换HTTP请求和响应,合适的默认配置可以开箱即用,例如对象自动转换为JSON(使用Jackson库)或XML(如果Jackson XML扩展可用,否则使用JAXB),字符串默认使用UTF-8编码。 可以使用Spring Boot的HttpMessageConverters类添加或自定义转换类: importorg.springframework.boot.autoconfigure.web.HttpMessageConverters; importorg.springframework.context.annotation.*; importorg.springframework.http.converter.*; @Configuration publicclassMyConfiguration{ @Bean publicHttpMessageConverterscustomConverters(){ HttpMessageConverter<?>additional=... HttpMessageConverter<?>another=... returnnewHttpMessageConverters(additional,another); } } 上下文中出现的所有HttpMessageConverterbean都将添加到converters列表,你可以通过这种方式覆盖默认的转换器列表(converters)。 自定义JSON序列化器和反序列化器 如果使用Jackson序列化,反序列化JSON数据,你可能想编写自己的JsonSerializer和JsonDeserializer类。自定义序列化器(serializers)通常通过Module注册到Jackson,但Spring Boot提供了@JsonComponent注解这一替代方式,它能轻松的将序列化器注册为Spring Beans。 MessageCodesResolver Spring MVC有一个实现策略,用于从绑定的errors产生用来渲染错误信息的错误码:MessageCodesResolver。Spring Boot会自动为你创建该实现,只要设置spring.mvc.message-codes-resolver.format属性为PREFIX_ERROR_CODE或POSTFIX_ERROR_CODE(具体查看DefaultMessageCodesResolver.Format枚举值)。 静态内容 默认情况下,Spring Boot从classpath下的/static(/public,/resources或/META-INF/resources)文件夹,或从ServletContext根目录提供静态内容。这是通过Spring MVC的ResourceHttpRequestHandler实现的,你可以自定义WebMvcConfigurerAdapter并覆写addResourceHandlers方法来改变该行为(加载静态文件)。 在单机web应用中,容器会启动默认的servlet,并用它加载ServletContext根目录下的内容以响应那些Spring不处理的请求。大多数情况下这都不会发生(除非你修改默认的MVC配置),因为Spring总能够通过DispatcherServlet处理这些请求。 你可以设置spring.resources.staticLocations属性自定义静态资源的位置(配置一系列目录位置代替默认的值),如果你这样做,默认的欢迎页面将从自定义位置加载,所以只要这些路径中的任何地方有一个index.html,它都会成为应用的主页。 此外,除了上述标准的静态资源位置,有个例外情况是Webjars内容。任何在/webjars/**路径下的资源都将从jar文件中提供,只要它们以Webjars的格式打包。 注如果你的应用将被打包成jar,那就不要使用src/main/webapp文件夹。尽管该文件夹是通常的标准格式,但它仅在打包成war的情况下起作用,在打包成jar时,多数构建工具都会默认忽略它。 Spring Boot也支持Spring MVC提供的高级资源处理特性,可用于清除缓存的静态资源或对WebJar使用版本无感知的URLs。 如果想使用针对WebJars版本无感知的URLs(version agnostic),只需要添加webjars-locator依赖,然后声明你的Webjar。以jQuery为例,"/webjars/jquery/dist/jquery.min.js"实际为"/webjars/jquery/x.y.z/dist/jquery.min.js",x.y.z为Webjar的版本。 注如果使用JBoss,你需要声明webjars-locator-jboss-vfs依赖而不是webjars-locator,否则所有的Webjars将解析为404。 以下的配置为所有的静态资源提供一种缓存清除(cache busting)方案,实际上是将内容hash添加到URLs中,比如<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>: spring.resources.chain.strategy.content.enabled=true spring.resources.chain.strategy.content.paths=/** 注实现该功能的是ResourceUrlEncodingFilter,它在模板运行期会重写资源链接,Thymeleaf,Velocity和FreeMarker会自动配置该filter,JSP需要手动配置。其他模板引擎还没自动支持,不过你可以使用ResourceUrlProvider自定义模块宏或帮助类。 当使用比如JavaScript模块加载器动态加载资源时,重命名文件是不行的,这也是提供其他策略并能结合使用的原因。下面是一个"fixed"策略,在URL中添加一个静态version字符串而不需要改变文件名: spring.resources.chain.strategy.content.enabled=true spring.resources.chain.strategy.content.paths=/** spring.resources.chain.strategy.fixed.enabled=true spring.resources.chain.strategy.fixed.paths=/js/lib/ spring.resources.chain.strategy.fixed.version=v12 使用以上策略,JavaScript模块加载器加载"/js/lib/"下的文件时会使用一个固定的版本策略"/v12/js/lib/mymodule.js",其他资源仍旧使用内容hash的方式<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>。查看ResourceProperties获取更多支持的选项。 欢迎页面 Spring Boot支持静态和模板欢迎页面。它首先index.html在配置的静态内容位置中查找文件。如果找不到,则会查找index模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。、 自定义Favicon Spring Bootfavicon.ico在配置的静态内容位置和类路径的根目录(按此顺序)中查找a。如果存在这样的文件,它会自动用作应用程序的图标。 路径匹配和内容协商 Spring MVC可以通过查看请求路径并将它匹配到应用程序中定义的映射(例如@GetMappingController方法上的注释),将传入的HTTP请求映射到处理程序。 Spring Boot选择默认禁用后缀模式匹配,这意味着请求"GET /projects/spring-boot.json"不会匹配@GetMapping("/projects/spring-boot")映射。这被认为是Spring MVC应用程序的最佳实践。此功能在过去对于没有发送正确的“Accept”请求标头的HTTP客户端来说非常有用;我们需要确保将正确的内容类型发送到客户端。如今,内容协商更可靠。 还有其他一些方法可以处理不一致地发送适当的“接受”请求标头的HTTP客户端。我们可以使用查询参数来确保类似的请求"GET /projects/spring-boot?format=json"将映射到@GetMapping("/projects/spring-boot")以下内容,而不是使用后缀匹配: spring.mvc.contentnegotiation.favor-parameter=true #我们可以更改参数名称,默认为“格式”: #spring.mvc.contentnegotiation.parameter-name=myparam #我们还可以通过以下方式注册其他文件扩展名/媒体类型: spring.mvc.contentnegotiation.media-types.markdown=text/markdown 如果您了解注意事项并仍然希望应用程序使用后缀模式匹配,则需要进行以下配置: spring.mvc.contentnegotiation.favor-path-extension=true #您也可以将该功能限制为已知扩展 #spring.mvc.pathmatch.use-registered-suffix-pattern=true #我们还可以通过以下方式注册其他文件扩展名/媒体类型: #spring.mvc.contentnegotiation.media-types.adoc=text/asciidoc ConfigurableWebBindingInitializer Spring MVC使用WebBindingInitializer为每个特殊的请求初始化相应的WebDataBinder,如果你创建自己的ConfigurableWebBindingInitializer @Bean,Spring Boot会自动配置Spring MVC使用它。 模板引擎 正如REST web服务,你也可以使用Spring MVC提供动态HTML内容。Spring MVC支持各种各样的模板技术,包括Velocity, FreeMarker和JSPs,很多其他的模板引擎也提供它们自己的Spring MVC集成。 Spring Boot为以下的模板引擎提供自动配置支持: FreeMarker Groovy Thymeleaf Velocity(1.4已不再支持) Mustache 注:由于在内嵌servlet容器中使用JSPs存在一些已知的限制,所以建议尽量不使用它们。 使用以上引擎中的任何一种,并采用默认配置,则模块会从src/main/resources/templates自动加载。 注:IntelliJ IDEA根据你运行应用的方式会对classpath进行不同的排序。在IDE里通过main方法运行应用,跟从Maven,或Gradle,或打包好的jar中运行相比会导致不同的顺序,这可能导致Spring Boot不能从classpath下成功地找到模板。如果遇到这个问题,你可以在IDE里重新对classpath进行排序,将模块的类和资源放到第一位。或者,你可以配置模块的前缀为classpath*:/templates/,这样会查找classpath下的所有模板目录。 错误处理 Spring Boot默认提供一个/error映射用来以合适的方式处理所有的错误,并将它注册为servlet容器中全局的 错误页面。对于机器客户端(相对于浏览器而言,浏览器偏重于人的行为),它会产生一个具有详细错误,HTTP状态,异常信息的JSON响应。对于浏览器客户端,它会产生一个白色标签样式(whitelabel)的错误视图,该视图将以HTML格式显示同样的数据(可以添加一个解析为'error'的View来自定义它)。为了完全替换默认的行为,你可以实现ErrorController,并注册一个该类型的bean定义,或简单地添加一个ErrorAttributes类型的bean以使用现存的机制,只是替换显示的内容。 注BasicErrorController可以作为自定义ErrorController的基类,如果你想添加对新context type的处理(默认处理text/html),这会很有帮助。你只需要继承BasicErrorController,添加一个public方法,并注解带有produces属性的@RequestMapping,然后创建该新类型的bean。 你也可以定义一个@ControllerAdvice去自定义某个特殊controller或exception类型的JSON文档: @ControllerAdvice(basePackageClasses=FooController.class) publicclassFooControllerAdviceextendsResponseEntityExceptionHandler{ @ExceptionHandler(YourException.class) @ResponseBody ResponseEntity<?>handleControllerException(HttpServletRequestrequest,Throwableex){ HttpStatusstatus=getStatus(request); returnnewResponseEntity<>(newCustomErrorType(status.value(),ex.getMessage()),status); } privateHttpStatusgetStatus(HttpServletRequestrequest){ IntegerstatusCode=(Integer)request.getAttribute("javax.servlet.error.status_code"); if(statusCode==null){ returnHttpStatus.INTERNAL_SERVER_ERROR; } returnHttpStatus.valueOf(statusCode); } } 在以上示例中,如果跟FooController相同package的某个controller抛出YourException,一个CustomerErrorType类型的POJO的json展示将代替ErrorAttributes展示。 自定义错误页面 如果想为某个给定的状态码展示一个自定义的HTML错误页面,你需要将文件添加到/error文件夹下。错误页面既可以是静态HTML(比如,任何静态资源文件夹下添加的),也可以是使用模板构建的,文件名必须是明确的状态码或一系列标签。 例如,映射404到一个静态HTML文件,你的目录结构可能如下: src/ +-main/ +-java/ |+<sourcecode> +-resources/ +-public/ +-error/ |+-404.html +-<otherpublicassets> 使用FreeMarker模板映射所有5xx错误,你需要如下的目录结构: src/ +-main/ +-java/ |+<sourcecode> +-resources/ +-templates/ +-error/ |+-5xx.ftl +-<othertemplates> 对于更复杂的映射,你可以添加实现ErrorViewResolver接口的beans: publicclassMyErrorViewResolverimplementsErrorViewResolver{ @Override publicModelAndViewresolveErrorView(HttpServletRequestrequest, HttpStatusstatus,Map<String,Object>model){ //UsetherequestorstatustooptionallyreturnaModelAndView return... } } 你也可以使用Spring MVC特性,比如@ExceptionHandler方法和@ControllerAdvice,ErrorController将处理所有未处理的异常。 映射Spring MVC以外的错误页面 对于不使用Spring MVC的应用,你可以通过ErrorPageRegistrar接口直接注册ErrorPages。该抽象直接工作于底层内嵌servlet容器,即使你没有Spring MVC的DispatcherServlet,它们仍旧可以工作。 @Bean publicErrorPageRegistrarerrorPageRegistrar(){ returnnewMyErrorPageRegistrar(); } //... privatestaticclassMyErrorPageRegistrarimplementsErrorPageRegistrar{ @Override publicvoidregisterErrorPages(ErrorPageRegistryregistry){ registry.addErrorPages(newErrorPage(HttpStatus.BAD_REQUEST,"/400")); } } 注.如果你注册一个ErrorPage,该页面需要被一个Filter处理(在一些非Spring web框架中很常见,比如Jersey,Wicket),那么该Filter需要明确注册为一个ERROR分发器(dispatcher),例如: @Bean publicFilterRegistrationBeanmyFilter(){ FilterRegistrationBeanregistration=newFilterRegistrationBean(); registration.setFilter(newMyFilter()); ... registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); returnregistration; } (默认的FilterRegistrationBean不包含ERRORdispatcher类型)。 WebSphere应用服务器的错误处理 当部署到一个servlet容器时,Spring Boot通过它的错误页面过滤器将带有错误状态的请求转发到恰当的错误页面。request只有在response还没提交时才能转发(forwarded)到正确的错误页面,而WebSphere应用服务器8.0及后续版本默认情况会在servlet方法成功执行后提交response,你需要设置com.ibm.ws.webcontainer.invokeFlushAfterService属性为false来关闭该行为。 Spring HATEOAS 如果正在开发基于超媒体的RESTful API,你可能需要Spring HATEOAS,而Spring Boot会为其提供自动配置,这在大多数应用中都运作良好。 自动配置取代了@EnableHypermediaSupport,只需注册一定数量的beans就能轻松构建基于超媒体的应用,这些beans包括LinkDiscoverers(客户端支持),ObjectMapper(用于将响应编排为想要的形式)。ObjectMapper可以根据spring.jackson.*属性或Jackson2ObjectMapperBuilderbean进行自定义。 通过注解@EnableHypermediaSupport,你可以控制Spring HATEOAS的配置,但这会禁用上述ObjectMapper的自定义功能。 CORS支持 跨域资源共享(CORS)是一个大多数浏览器都实现了的W3C标准,它允许你以灵活的方式指定跨域请求如何被授权,而不是采用那些不安全,性能低的方式,比如IFRAME或JSONP。 从4.2版本开始,Spring MVC对CORS提供开箱即用的支持。不用添加任何特殊配置,只需要在Spring Boot应用的controller方法上注解@CrossOrigin,并添加CORS配置。通过注册一个自定义addCorsMappings(CorsRegistry)方法的WebMvcConfigurerbean可以指定全局CORS配置: @Configuration publicclassMyConfiguration{ @Bean publicWebMvcConfigurercorsConfigurer(){ returnnewWebMvcConfigurerAdapter(){ @Override publicvoidaddCorsMappings(CorsRegistryregistry){ registry.addMapping("/api/**"); } }; } } 本文相关视频连接

优秀的个人博客,低调大师

【熵教育】SpringBoot的配置外部化——熵学院

在前面的课程中,我们给大家分享过SpringBoot精妙的启动配置,主要阐述的是spring的IoC容器在SpringBoot中的加载过程,并与传统项目中Spring的IoC容器加载过程进行了一个对比.我们在开发的过程中,除了IoC容器的配置之外,当然还有许多其他的配置,诸如数据库的链接信息,端口,以及项目的内部使用的一些个性化信息等.那SpringBoot是如何管理这些配置呢?我今天呢,就从以下这三个方面来给大家分享一下SpringBoot是如何管理配置信息的. 配置文件和属性获取 配置文件的名字、目录和优先级 传统的properties与YAML 1.配置文件和属性获 1.1 传统配置文件的值获取与SpringBoot中的值获取 在传统的项目里,我们的配置信息一般都写在配置文件中,然后关于spring需要的信息,我们就在spring的xml文件里引用,大略如下所示: classpath下的config里创建一个jdbc.properties jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true jdbc.username=develop jdbc.password=&dT$BvYdOlH4*m9G 然后在我们的application.xml里引入我们需要的这个数据源: <?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:property-placeholderlocation="classpath:config/jdbc.properties"/> <beanid="dataSource"class="com.alibaba.druid.pool.DruidDataSource"> <propertyname="driverClassName"value="${jdbc.driverClassName}"/> <propertyname="url"value="${jdbc.url}"/> <propertyname="username"value="${jdbc.username}"/> <propertyname="password"value="${jdbc.password}"/> </bean> ......其他配置 </beans> 当然除了spring的数据源信息之外,我们往往也会有一些其他的信息,比如项目返回给前端的一些报错信息等,此时我们通常的做法是使用java的原生方法去加载。如以下所示: 在classpath的config下有个webreslt.properties 0=SUCCESS 100000=参数有误 100001=AppKey不能为空 100002=Signature不能为空 100003=参数列表(paramMap)不能为空 100004=secret不能包含在参数列表中 100005=参数签名不合法 100006=手机号不能为空 100007=验证码不能为空 100008=邮件内容不能为空 100009=收件人不能为空 100010=邮件主题不能为空 200000=应用无权限 200001=应用未注册 300000=Api异常 300001=短信发送失败 300002=短信验证失败 300003=邮件发送失败 300004=短信发送超过最大条数限制 获取值的方法如下: packagecom.ailu.paas.common.utils; importorg.apache.commons.lang3.StringUtils; importjava.util.Locale; importjava.util.ResourceBundle; publicclassPropertyFileReader{ publicstaticStringgetItem(Stringkey){ returngetItem(key,""); } publicstaticStringgetItem(Stringkey,StringdefaultValue){ ResourceBundlerb=ResourceBundle.getBundle("config/webresult"); Stringvalue=""; try{ value=newString(rb.getString(key).getBytes("ISO-8859-1"),"UTF-8"); }catch(Exceptione){ e.printStackTrace(); } if(StringUtils.isEmpty(value)){ value=defaultValue; } returnvalue.trim(); } } 然后在其他的地方,我们就可以直接使用以下这样的方式去获取属性文件中的值了. Stringvalue=PropertyFileReader.getItem(key); 而在SpringBoot中呢,我们则可以使用@Component+@Value这两个组合,来快速的读取配置文件中的值, 仍然是在classpath下,我们在配置文件里写上如下配置: name="Lianmengtu" 然后我们创建一个java类,并加上@Component和@Value,如下所示: packagetop.lianmengtu.testprofile.common; importlombok.Getter; importlombok.Setter; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.stereotype.Component; @Component @Getter @Setter publicclassTestProperty{ @Value("${name}") privateStringname; } 其中@Component是为了把TestProperty作为组件放入到Spring的IoC容器中,而@Value("${name}")则是为了从配置文件中取值,${name}中的name即是配置文件中的key.然后我们就可以在其他地方通过注入直接使用了: @Autowired privateTestPropertytestProperty; publicvoidtest(){ System.out.println(testProperty.getName()); } 1.2 随机值的绑定 在某些场景下,我们可能需要在项目的配置中添加一些随机值,并且这些值在我们项目启动后就自动的初始化,而SpringBoot就考虑到了这种情况,于是给我们准备了一些工具,方便我们的使用.使用情况如下: #随机字符串 secret=${random.value} #随机数 setup=${random.int} #0-10之间的随机数 range-int=${random.int[0,10]} #生成uuid uuid=${random.uuid} 获取方式和其他的属性相同,如下所示: packagetop.lianmengtu.testprofile.common; importlombok.Getter; importlombok.Setter; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.stereotype.Component; @Component @Getter @Setter publicclassTestProperty{ @Value("${secret}") privateStringsecret; @Value("${setup}") privateIntegersetup; @Value("${range-int}") privateIntegerrangeInt; @Value("${uuid}") privateStringuuid; } 1.3 变量的引用与占位符 有些时候我们会在配置项中引用另一项的值,当然,是以变量的形式进行引用.如下所示: protocal=http:// domain=${protocal}ask.lianmengtu.top 这里我们看到,在springBoot中使用的变量占位符是${key}.这时候就有一个问题,因为我们现在大多数的开发环境都是maven,我们都知道maven也是支持变量占位的,我们可以在打包不同环境的时候可以激活不同的profile,然后对变量值进行替换,而我们往常在使用Maven的时候,用的变量占位符正好是${key},那此时,我们该怎么办呢?SpringBoot是不是不支持Maven的变量占位呢?我们必须要二选其一吗? 当然不是.SpringBoot也考虑到了这个问题,因此给maven占位提供了另外一个符号即@key@,如下所示: protocal=http:// domain=${protocal}ask.lianmengtu.top username=@username@ password=@password@ 然后我们就可以在我们的pom里这么写了,而当我们激活某一个profile的时候,相应的maven变量就会被替换了 <profiles> <profile> <id>dev</id> <properties> <username>dev</username> <password>dev123</password> </properties> </profile> <profile> <id>test</id> <properties> <username>test</username> <password>test123</password> </properties> </profile> </profiles> 2. yml与properties SpringBoot是除了支持properties这种方式之外,它也支持YAML这种方式,并且因为yml结构清晰,又可以继承,所以使用yml这种方式的人也越来越多了.而我就是这么对yml路转粉的. 2.1 yml结构化 yml的第一个好处是结构化,这与properties是明显的差别.这里放上两份同样的配置文件来个视觉对比,首先是properties environments.dev.url= environments.dev.name=DeveloperSetup environments.prod.url= environments.prod.name=MyCoolApp my.servers[0]=dev.example.com my.servers[1]=another.example.com 对比yml environments: dev: url:http://dev.example.com name:DeveloperSetup prod: url:http://another.example.com name:MyCoolApp my: servers: -dev.example.com -another.example.com 这里只是举个例子,所以可能这两三行大家看起来没什么感觉,但要知道在实际的项目中,我们的配置可能包含各种各样的信息,数据库的,缓存的,第三方平台的等等,那时候,如果我们还用properties,那么看着将会非常头大. 2.2 yml的继承 在我们使用配置文件的时候,尤其是分环境使用的时候,常常会碰到这么一个问题: 大多数的项目配置都一样,只有少数的不一样,此时,如果是使用properties,那么我们就只能每个文件各写一份,而在yml里,我们就不用,我们只需要将通用的那部分写到application-common.yml里,然后少量不同的,我们在分环境进行描述,application-dev.yml,application-prod.yml里,这样我们只需要激活一份文件,剩下的就会自动的进行继承和使用了,具体方式如下: application.yml app: name:"lianmengtu" country:"China" username:"melon" password:"melon123" application-dev.yml: app: username:"jacobdev" password:"dev123456" application-prod.yml app: username:"LMTprod" password:"prod456" 这样当我们在启动时,激活不同的配置时,username和password会不同,但name和country则是从默认的yml中继承过来的. 2.3 指定配置文件的名字和地址 刚刚我们提到过多种环境,配置文件之所以要区分环境,就是因为有些信息是需要保密的,无法公开.而SpringBoot则允许从外部,通过命令行的形式,对配置文件进行指定,当然也可以指定变量. 2.3.1 指定配置文件的名字 我们可以使用--spring.config.name=xxx 这样的参数形式指定配置文件的名字: $java-jarmyproject.jar--spring.config.name=myproject 2.3.2 配置指定目录下的配置文件 我们可以使用--spring.config.location=xxxxx这样的参数形式来配置指定目录下的配置文件,如下文则指定了classpath下的config目录下的test.yml java-jarmyproject.jar--spring.config.location=classpath:/config/test.yml 当然从1.x转过来的人可能更喜欢指定目录,这里要特别说明一下,如果--spring.config.location是以目录结尾的,则必须加/ 如下所示: java-jarmyproject.jar--spring.config.location=classpath:/config/ 2.3.3 配置的优先级 在我们没有明确指定文件名字的时候,springBoot会按着以下顺序进行考虑 当前目录下的config目录里的application.properties 当前目录下的application.properties classpath下的config目录下的application.properties classpath下的application.properties 当然除了这些之外,命令行也是可以传参数的,并且命令行参数的优先级是最高的. 3. 一些比较复杂的配置 使用@Value()来进行属性注入有些时候会显得比较笨重,尤其是使用多个配置或者我们的配置项是呈垂直结构化的数据时,更是这样.SpringBoot提供了另外一种方法来处理这类比较复杂的数据.这就是我们要说的@ConfigurationProperties. 首先我们有这样一个配置文件: app: name:"lianmengtu" enabled:false security: username:"jacob" password:"jacob123" roles: -USER -ADMIN 我们看到在这个配置文件里,app下有name属性,有enabled属性,其中较为特殊的是security,因为他还包含了些其他的属性,包括username,包括password,还有一个类型为String的roles集合,那么此时,我们可以对应的写成下面这个属性类 packagetop.lianmengtu.testprofile.common; importorg.springframework.boot.context.properties.ConfigurationProperties; importorg.springframework.stereotype.Component; importjava.util.ArrayList; importjava.util.Collections; importjava.util.List; @ConfigurationProperties("app") publicclassComplexProperty{ privateStringname; privatebooleanenabled; privatefinalSecuritysecurity=newSecurity(); publicStringgetName(){ returnname; } publicvoidsetName(Stringname){ this.name=name; } publicbooleanisEnabled(){ returnenabled; } publicvoidsetEnabled(booleanenabled){ this.enabled=enabled; } publicSecuritygetSecurity(){ returnsecurity; } publicstaticclassSecurity{ privateStringusername; privateStringpassword; privateList<String>roles=newArrayList<>(Collections.singleton("USER")); publicStringgetUsername(){ returnusername; } publicvoidsetUsername(Stringusername){ this.username=username; } publicStringgetPassword(){ returnpassword; } publicvoidsetPassword(Stringpassword){ this.password=password; } publicList<String>getRoles(){ returnroles; } publicvoidsetRoles(List<String>roles){ this.roles=roles; } } } 之后,我们可以在我们的service层引用它: packagetop.lianmengtu.testprofile.service.impl; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Service; importtop.lianmengtu.testprofile.common.ComplexProperty; importjava.util.List; @Service publicclassPropertyService{ privatefinalComplexPropertycomplexProperty; @Autowired publicPropertyService(ComplexPropertycomplexProperty){ this.complexProperty=complexProperty; } publicStringname(){ returncomplexProperty.getName(); } publicbooleanenabled(){ returncomplexProperty.isEnabled(); } publicStringuserName(){ returncomplexProperty.getSecurity().getUsername(); } publicStringpassword(){ returncomplexProperty.getSecurity().getPassword(); } publicStringroles(){ StringBufferroles=newStringBuffer(); List<String>roleArray=complexProperty.getSecurity().getRoles(); roleArray.forEach(role->{ roles.append(role).append("----"); }); returnroles.toString(); } } 这里的构造函数注入,我们也可以换成相应的@Autowried注入.为了使这个配置生效,我们需要在Application上加上@EnableConfigurationProperties(ComplexProperty.class) packagetop.lianmengtu.testprofile; importorg.springframework.boot.SpringApplication; importorg.springframework.boot.autoconfigure.SpringBootApplication; importorg.springframework.boot.context.properties.EnableConfigurationProperties; importtop.lianmengtu.testprofile.common.ComplexProperty; @SpringBootApplication @EnableConfigurationProperties(ComplexProperty.class) publicclassApplication{ publicstaticvoidmain(String[]args){ SpringApplication.run(Application.class,args); } } 刚刚举的那种类型可以说是比较复杂的一个类型了,除了那种内嵌的形式之外,如果说我们只想得到内嵌类里面的属性或者说只有内嵌类里面有属性,则我们可以写成以下这种形式,其他的地方都不用变.这种形式我们称之为relaxed binding @ConfigurationProperties("app.security") publicclassComplexProperty{ privateStringusername; privateStringpassword; privateList<String>roles; publicStringgetUsername(){ returnusername; } publicvoidsetUsername(Stringusername){ this.username=username; } publicStringgetPassword(){ returnpassword; } publicvoidsetPassword(Stringpassword){ this.password=password; } publicList<String>getRoles(){ returnroles; } publicvoidsetRoles(List<String>roles){ this.roles=roles; } } 并且也支持map类型,配置文件如下所示,其中key可以有两种指定方式,一种是"[/key]",一种是/key: app: name:"lianmengtu" enabled:false security: username:"jacob" password:"jacob123" roles: -USER -ADMIN work: "[/position]":"CEO" "[/company]":"bat" /address:"BeiJing" 而我们的配置类则如下所示: packagetop.lianmengtu.testprofile.common; importorg.springframework.boot.context.properties.ConfigurationProperties; importjava.util.List; importjava.util.Map; @ConfigurationProperties("app.security") publicclassComplexProperty{ privateStringusername; privateStringpassword; privateList<String>roles; privateMap<String,String>work; publicStringgetUsername(){ returnusername; } publicvoidsetUsername(Stringusername){ this.username=username; } publicStringgetPassword(){ returnpassword; } publicvoidsetPassword(Stringpassword){ this.password=password; } publicList<String>getRoles(){ returnroles; } publicvoidsetRoles(List<String>roles){ this.roles=roles; } publicMap<String,String>getWork(){ returnwork; } publicvoidsetWork(Map<String,String>work){ this.work=work; } } 这两种方式都是Ok的, 总结 今天的内容,其实到这里就已经结束了,在使用配置文件的过程中,我们还碰到了一些问题,首先,我们在使用这种配置方式,无论是@Component+@Value还是后来的@ConfigurationProperties这两种方式,他都是在进入spring的时候进行初始化的,这也就意味着,如果我们没有从Spring的容器中去取我们的属性容器的话,那么我们的属性值是没有办法注入的,这一点希望大家能够注意,其次,今天只是讲了主要的几种方式,还有一些像复杂类型的属性合并,以及属性验证,这些希望大家可以研究一下,如果有不明白的,欢迎大家在论坛上提出来,我们可以一起探讨.以下是@ConfigurationProperties和@Value的一些对比: Feature @ConfigurationProperties @Value Relaxed binding Yes No Meta-data support Yes No SpELevaluation No Yes 转载请注明出处:联盟兔

优秀的个人博客,低调大师

【熵教育】Anders-SpringBoot中的Http应用:WebFlux——熵学院

我们今天开始进入Spring WebFlux.WebFlux是Spring5.0开始引入的.有别于SpringMVC的Servlet实现,它是完全支持异步和非阻塞的.在正式使用Spring WebFlux之前,我们首先得了解他和Servlet的区别,以及他们各自的优势,这样我们才能够给合适的场景选择合适的开发工具. 首先我们要问几个问题,为什么要有异步?在异步之前,软件行业做过哪些努力,他们的优势是什么?基于这几个问题,我们今天分享以下三个知识点: 从Http1.X 到Http2.0 从Servlet2.x到Servlet3.x WebFlux的出场 1. 从Http1.x到Http2.0 异步和同步是无法分开的.他们对性能的理解和处理也是各有千秋.传统的web项目因为是基于阻塞I/O模型而建立的,所以他们只能通过对整个链路的优化来提升性能,而这里的性能就包括了伸缩性和响应速度.这里面比较重要的一个环节就是网络传输.相对而言,这也是距离我们的用户最近的一个环节,因此他们对并发的处理以及对响应速度的处理就比其他的会更直接地影响我们的用户. 1.1 Http/1.x 在http1.x中,我们都知道,http会先进行三次握手,握手成功之后,开始传递数据,服务器响应完毕,就进行四次挥手,最后关闭链接.刚开始应用这个概念的时候,是非常受欢迎的,因为在那时候传递的还是静态页面或者动态数据比较少的资源,因此无论是客户端还是服务器端,他都节省了更多的资源.但随着互联网的飞速发展,这种方式就遇到了问题.如果每次传递数据都需要三次握手四次挥手的话,那么随着数据访问量的增加,那么三次握手四次挥手带来的资源消耗就会成为影响系统的瓶颈.这就好像一根针重量可以忽略,但当我们聚集上亿根针的时候,那么他的重量和所占用的空间,就成了必须要考虑的问题了. 那能不能建立好一次链接之后,我多传递几次数据,然后在关闭呢?当然可以,这就是长链接,也就是大家常说的"Keep-Alive".而HTTP1.1则是默认就开启了Keep-Alive.Keep-Alive虽然暂时性的解决了建立链接所带来的开销,也一定程度的提高了响应速度,但后来又凸显了另外两个问题: 首先,因为http是串行文件传输.所以当客户端请求a文件时,b文件只能等待.等待a链接到服务器,服务器处理文件,服务器返回文件这三个步骤完成后,b才能接着处理.我们假设,链接服务器,服务器处理,服务器返回各需要1秒,那么b处理完的时候就需要6秒,以此类推.(当然,这里有个前提,服务器和浏览器都是单通道的.)这就是我们说的阻塞. 其次,链接数的问题.我们都知道服务器的链接数是有限的.并且浏览器也对链接数有限制.这样能接入进来的服务就是有个数限制的,当达到这个限制的时候,其他的就需要等待链接被断开,然后新的请求才能够进入.这个比较容易理解. 之所以http1.x会使用串行文件传输,是因为http传输的无论是request还是response都是基于文本的,所以接收端无法知道数据的顺序,因此必须按着顺序传输.这也就限制了只要请求就必须新建立一个链接,这也就导致了第二个问题的出现. 1.2 Http/2 为了从根本上行解决http1.x所遗留的这两个问题,http2引入了二进制数据帧和流的概念.其中帧的作用就是对数据进行顺序标识,这样的话,接收端就可以根据顺序标识来进行数据合并了.同时,因为数据有了顺序,服务器和客户端就可以并行的传输数据,而这就是流所作的事情. 这样,因为服务器和客户端可以借助流进行并行的传递数据,那么同一台客户端就可以使用一个链接来进行传输,此时服务器能处理的并发数就有了质的飞跃. http/2的这个新特性,就是多路复用.我们可以看到,多路复用的本质就是并行传输.那web对请求的处理是否可以使用这个思路呢? 2.Servlet 现在我们来讨论Servlet与Netty.这两个一个主要是以同步阻塞的方式服务的,另一个是异步非阻塞的.这也就造成了他们适用的场景是不同的. 2.1 Servlet 做JavaWeb研发的几乎没有不知道Servlet的.在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。为了解决这一的问题,Servlet3.0引入了异步处理. 在Servlet 3.0中,我们可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。 这里举个例子,对于一个需要完成长时处理的Servlet来说,其实现通常为: packagetop.lianmengtu.testjson.servlet; importjavax.servlet.ServletException; importjavax.servlet.annotation.WebServlet; importjavax.servlet.http.HttpServlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importjava.io.IOException; //@WebServlet("/syncHello"),因为使用的SpringBoot模拟,所以注释掉该注解 publicclassMyServletextendsHttpServlet{ @Override protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{ super.doGet(req,resp); newLongRunningProcess().run(); System.out.println("HelloWorld"); } } LongRunningProcess实现如下: packagetop.lianmengtu.testjson.servlet; importjava.util.concurrent.ThreadLocalRandom; publicclassLongRunningProcess{ publicvoidrun(){ try{ intmillis=ThreadLocalRandom.current().nextInt(2000); StringcurrentThread=Thread.currentThread().getName(); System.out.println(currentThread+"sleepfor"+millis+"milliseconds."); Thread.sleep(millis); }catch(InterruptedExceptione){ e.printStackTrace(); } } } 我们现在将MyServlet注入到Spring容器中: @Bean publicServletRegistrationBeanservletRegistrationBean(){ returnnewServletRegistrationBean(newMyServlet(),"/syncHello"); } 此时的SyncHelloServlet将顺序地先执行LongRunningProcess的run()方法,然后在控制台打印HelloWorld.而3.0则提供了对异步的支持,因此在Servlet3.0中我们可以这么写: @Override protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{ AsyncContextasyncContext=req.startAsync(); asyncContext.start(()->{ newLongRunningProcess().run(); try{ asyncContext.getResponse().getWriter().print("HelloWorld"); }catch(IOExceptione){ e.printStackTrace(); } asyncContext.complete(); }); } 此时,我们先通过request.startAsync()获取到该请求对应的AsyncContext,然后调用AsyncContext的start()方法进行异步处理,处理完毕后需要调用complete()方法告知Servlet容器。start()方法会向Servlet容器另外申请一个新的线程(可以是从Servlet容器中已有的主线程池获取,也可以另外维护一个线程池,不同容器实现可能不一样),然后在这个新的线程中继续处理请求,而原先的线程将被回收到主线程池中。事实上,这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程。 Servlet 3.0对请求的处理虽然是异步的,但是对InputStream和OutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO,通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。 虽然Servlet3.1提供了异步的方式,并且做的也比Servlet3.0更彻底,但是如果我们使用了Servlet3.1提供的异步接口,像刚刚的代码演示的那样,那么我们在之后的处理中就没有办法再使用他原来的接口了.这就让我们处于了一种非此即彼的状况中.如果是这样,Servlet系列的技术,如SpringMVC也就是这样了.那怎么办呢? 3. WebFlux的出场 现在我们会从以下几个层面来探讨WebFlux 为什么要有WebFlux? Reactive定义与ReactiveAPI WebFlux中的性能问题 WebFlux的并发模型 WebFlux的适用性 3.1为什么要有WebFlux 首先,为什么要有webFlux? 在前面两部分,我们一直在探讨并发问题.为了解决并发,我们需要使用非阻塞的web技术栈.因为非阻塞的web栈使用的线程数更少,对硬件资源的要求更低.虽然Servlet3.1为非阻塞I/O提供了一些支持,但刚刚我们提到了,如果我们使用Servlet3.1里的非阻塞API,会导致我们无法再使用它原来的API.并且,自从非阻塞I/O以及异步概念出现之后,就诞生了一批专为异步和非阻塞I/O设计的服务器,比如Netty,这就催生了新的能服务于各种非阻塞I/O服务器的统一的API. WebFlux诞生的另一个重要原因是函数式程序设计.随着脚本型语言(Nodejs,Angular等)的扩张,函数式程序设计以及后继式API也相继火起来.以至于Java也在Java8中引入了Lambda来对函数式程序设计进行支持,又引入了StreamAPI来对后继式程序进行支持.由此,对具备函数式编程和后继式程序设计的Web框架的需求也越来越大了。 3.2Reactive的定义与API Reactive的定义 我们接触了"非阻塞"和"函数式",那reactive是什么意思呢? "reactive"这个术语指的是:围绕着对改变做出响应的程序设计模型---网络组件对IO事件做出响应,UIController对鼠标事件做出响应等等.在那种情况下,非阻塞取代了阻塞是响应式的,我们正处于响应模式中,当操作完成和数据变得可用的时候发起通知. 还有另一个重要的机制那就是我们在spring team里整合"reactive"以及非阻塞式背压机制.在同步里,命令式的代码,阻塞式地调用服务为普通的表单充当背压机制强迫调用者等待.在非阻塞式编程中,控制事件的频率就变得很重要防止快速的生产者不会压垮他的目的地. Reactive Streams 是一个定义了使用背压机制的异步组件之间交互设计的小型说明书(在Java9中也采纳了).例如,一个数据仓库(可以看做Publisher)可以生产数据,然后HTTP Server(看做订阅者)可以写入到响应里.Reactive Streams的主要目的是让订阅者可以控制生产者产生数据的速度有多快或有多慢. Reactive API Reactive Streams 在互操作性上扮演了一个很重要的角色.类库和基础设施组件虽然有趣,但对于应用程序API来说却用处甚少,因为他们太底层了.应用程序需要一个更高级别更丰富的函数式API来编写异步逻辑---和Java8里的StreamAPI很类似,不过不仅仅是为集合做准备的. Reactor 是为SpringWebFlux选择的一个reactive类库.它提供了Mono和Flux类型的API来处理0..1(Mono)和0..N(Flux)数据序列化通过一组丰富的操作集和ReactiveX vocabulary of operators对齐.Reactor 是一个Reactive Streams类库,所以他所有的操作都支持非阻塞背压机制.Reactor强烈地聚焦于Server端的Java.他在发展上和Spring有着紧密的协作. WebFlux要求Reactor作为一个核心依赖,但凭借Reactive Streams也可以和其他的reactive libraries一起使用.一般来说,一个WebFlux API 接收一个Publisher作为输入,转换给一个内置的Reactor类型来使用,最后返回一个Flux或一个Mono作为输出.所以,你可以批准任何的Publisher作为输入,你可以应用操作在输出上,但你因为你使用了其他的reactive library所以你需要进行转换.只要可行(例如,注解controllers),WebFlux可以在使用RXJava和另一个reactive library之间透明的改变.看Reactive Libraries获取更多地细节. 3.3 性能 性能这个词有很多特征和含义.Reactive 和非阻塞通常不会使应用程序运行地更快.在某些场景下,他们也可以.(例如,在并行条件下使用WebClient来执行远程调用的话).整体来说,非阻塞方式可能需要做更多的工作并且他也会稍微增加请求处理的时间. 对reactive和非阻塞好处的预期关键在于使用小,固定的线程数和更少的内存来扩展的能力.这使应用程序在加载的时候更加有弹性,因为他们以一种更可以预测的方式扩展.然而为了看到这些好处,你需要一些延迟(包括比较慢的不可预知的网络I/O).那是响应式堆栈开始显示他力量的地方,并且这些不同是非常吸引人的. 3.4并发模型 Spring MVC和Spring WebFlux都支持注解Controllers,但他们在并发模型和对阻塞和线程的默认呈现(assumptions)上是非常不同的.在Spring MVC(和通用的servlet应用)中,都假设应用程序是阻塞当前线程的(例如,远程调用),并且出于这个原因,servlet容器处理请求的期间使用一个巨大的线程池来吸收潜在的阻塞. 在Spring WebFlux(和非阻塞服务器)中,假设应用程序是非阻塞的,所以,非阻塞服务器使用小的,固定代销的线程池(event loop workders)来处理请求. "弹性伸缩"和"小数量的线程"或许听起来矛盾,但是对于不会阻塞当前线程(用依赖回调来取代)意味着你不需要额外的线程,因为非阻塞调用给处理了. 调用一个阻塞API 要是你需要使用阻塞库怎么办?Reactor和RxJava都提供了publishOn操作用一个不同的线程来继续处理.那意味着有一个简单的脱离舱口(一个可以离开非阻塞的出口).然而,请牢记,阻塞API对于并发模型来说不太合适. 易变的状态 在Reactor和RxJava里,你通过操作符生命逻辑,在运行时在不同的阶段里,都会形成一个进行数据序列化处理的管道.这样做的一个主要好处就是把应用程序从不同的状态保护中解放了出来,因为管道中的应用代码是绝不会被同时调用的. 线程模型 在运行了一个使用Spring WebFlux的服务器上,你期望看到什么线程呢? 在一个"vanilla"Spring WebFlux服务器上(例如,没有数据访问也没有其他可选的依赖),你能够看到一个服务器线程和几个其他的用来处理请求的线程(一般来说,线程的数目和CPU的核数是一样的).然而,Servlet容器在启动的时候就使用了更多的线程(例如,tomcat是10个),来支持servlet(阻塞)I/O和servlet3.1(非阻塞)I/O的用法. 响应式的WebClient操作是用Event Loop方式.所以你可以看到少量的固定数量的线程和他关联.(例如,使用了Reactor Netty连接的reactor-http-nio).然而,如果Reactor Netty在客户端和服务端都被使用了,这两者之间的event loop资源默认是被共享的. Reactor和RxJava提供了抽象化的线程池,调度器目的是结合publishOn操作符在不同的线程池之间切换操作.调度器有一个名字,建议这个名字是一个具体的并发策略--例如,"parallel"(因为CPU-bound使用有限的线程数来工作)或者"elastic"(因为I/O-bound使用大量的线程来工作).如果你看到这类的线程,这就意味着一些代码正在使用一个具体的使用了Scheduler策略的线程池. 数据访问库和其他第三方库依赖也创建和使用了他们自己的线程. 下次我们来分享Spring WebFlux的使用. 本文相关视频

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Oracle

Oracle

Oracle Database,又名Oracle RDBMS,或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是目前世界上流行的关系数据库管理系统,系统可移植性好、使用方便、功能强,适用于各类大、中、小、微机环境。它是一种高效率、可靠性好的、适应高吞吐量的数据库方案。

JDK

JDK

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。