SegmentFault springboot 最佳实践最新的文章
2020-12-21T19:56:17+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
03-IDE工具之IDEA中Git的基本应用。
https://segmentfault.com/a/1190000038570538
2020-12-21T19:56:17+08:00
2020-12-21T19:56:17+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
98
<h2>基于Git的项目操作</h2><h3>安装Git工具</h3><p>Git是版本控制系统,可以借助Git实现团队代码版本控制及管理,<br>从官方<a href="https://link.segmentfault.com/?enc=9S4C1zpYc3UDf3PLNvZvpA%3D%3D.ww9DW6h5fkvpOyC4Ym6qlYfuYu9jiMWcMHe6RJ6jXJp%2Bh9XDV3ZZNdCzR0z%2Bm7hyDsx8akmte24EXn%2Frq%2BHWDFQ9TwHmnv7ACeRymKz0oj1wEz1TtPAWuaLa4ZbarYSx4huVdZsJJZyGyBtypRuKaDLz%2BgMsp2BdZSiDdO4GzSJBL6nzbnBIdysrjZN6Dhox" rel="nofollow">https://www.git-scm.com/downl...</a>,如图所示:<br><img src="/img/bVcLZFt" alt="image.png" title="image.png"><br>Git下载完成以后,傻瓜式(一直下一步)安装即可,不要更改安装目录(假如已安装过则无须安装)。</p><h3>Git全局配置</h3><p>打开Git客户端工具,配置用户和密码,用于识别提交代码的用户。</p><pre><code>$ git config --global user.name "your-name"
$ git config --global user.email "your-email@youremail.com" </code></pre><p>检查配置信息</p><pre><code>$ git config --list
user.email=xxxxxx@xxxxxx.com
user.name=xxxxxx</code></pre><h3>Idea中检查Git配置。</h3><p>找到Git配置选项,进行Git测试,如图所示:</p><p><img src="/img/bVbRAEr" alt="image.png" title="image.png"></p><h3>IDEA项目中创建本地库</h3><p>创建项目本地库,如图所示:</p><p><img src="/img/bVcLZLf" alt="image.png" title="image.png"></p><p>一般本地库会创建在你项目的根目录,如图所示:</p><p><img src="/img/bVcLZ7J" alt="image.png" title="image.png"></p><p>本地库创建好以后会在项目的根目录添加一个.git目录(可能是隐藏目录。</p><h3>IDEA项目中本地库配置</h3><p>对.git目录中的exclude文件进行配置,对指定资源进行过滤(例如哪些资源不提交、上传,可以此文件做全局配置),内容如下:</p><pre><code>HELP.md
target/
out/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
.gitignore
### maven ###
mvnw
*.cmd
.mvn/
</code></pre><h3>项目Add,Commit操作</h3><p>将项目、Module更新添加到暂存区,提交(Commit)本地库,例如:</p><p><img src="/img/bVbPhY4" alt="image.png" title="image.png"></p><p>也可以,基于工具栏的按钮进行相关操作,如图所示:</p><p><img src="/img/bVbRAE0" alt="image.png" title="image.png"></p><h3>Idea中安装Gitee插件</h3><p>打开Setting中的Plugins选项,然后进行gitee插件搜索和安装,如图所示:<br><img src="/img/bVbPgOE" alt="image.png" title="image.png"></p><p>Gitee 安装成功以后,检查是否在Version Control中有Gitee选项,如图所示:<br><img src="/img/bVbPgP2" alt="image.png" title="image.png"></p><p>点击Version Control的Gitee选项,进入Gitee配置,如图所示:<br><img src="/img/bVbPdB6" alt="image.png" title="image.png"></p><p>在Gitee配置界面,选则添加账户(Add Account),进入账户配置界面,如图所示:<br><img src="/img/bVbPdBO" alt="image.png" title="image.png"></p><p>在Gitee账户配置界面,进行连接Gitee平台的账户配置(要事先注册好Gitee平台账户),然后点击Login进行登陆,登陆成功以后会呈现如下界面,如图所示:</p><p><img src="/img/bVbPdCs" alt="image.png" title="image.png"></p><h3>项目Push操作分析及实现</h3><p>将项目、Module推送的Gitee远程代码托管平台,<br>在这里分两种情况,一种情况是已经有远程仓库了,一种情况是暂时还没有远程仓库.<br>在没有远程仓库的情况下,我们可以直接给予CVS进行代码的分享,其过程如下:<br>如图所示:</p><p><img src="/img/bVbPhY7" alt="image.png" title="image.png"></p><p>指定Gitee仓库的仓库名(库不存在,推送时自动创建),如图所示:</p><p><img src="/img/bVbRAFY" alt="image.png" title="image.png"></p><p>登陆Gitee,检查远程仓库内容,如图所示:</p><p><img src="/img/bVbPhZK" alt="image.png" title="image.png"></p><h3>从Gitee地址克隆(Clone)项目</h3><p>打开IDEA中可克隆(clone)选项,如图所示:<br><img src="/img/bVbRAFr" alt="image.png" title="image.png"></p><p>指定克隆地址和克隆目录,假如要克隆到本地位置已经有一个同名的项目,则可以修改新的项目名,如图所示:<br><img src="/img/bVbRAFM" alt="image.png" title="image.png"></p><p>克隆完成以后,选择打开项目的窗口,例如:<br><img src="/img/bVbRAFQ" alt="image.png" title="image.png"></p><p>项目打开以后,配置JDK,MAVEN,自动编译,编码等,并将maven项目添加到maven区,如图所示:<br><img src="/img/bVcLZZk" alt="image.png" title="image.png"></p><p>也可以在项目的pom.xml文件上右键选择add as maven project,将项目添加到maven区。<br>假如是普通的java项目(非maven项目),此时还需要将src转换为sources root格式(选择src目录,右键选择mark directory as sources root).</p><h2>总结(Summary)</h2><p>在本小节中重点讲解了IDEA中Git的一个基本操作,通过Git在IDEA中实现了项目的Commit,Push,Update等操作.</p>
13-SpringBoot工程中ajax技术分析及应用
https://segmentfault.com/a/1190000037784309
2020-11-10T12:03:44+08:00
2020-11-10T12:03:44+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
102
<h2>Ajax 技术简介</h2><h3>背景分析?</h3><p>在互联网高速发展的今天,传统的WEB应用,对于高并发、高性能、高可靠性的要求已迫在眉睫。单线程方式的客户端与服务端交互方式已经不能满足现阶段的需求.我们需要以异步、按需加载的方式从服务端获取数据并及时刷新,来提高用户体验,于是Ajax技术诞生。</p><h3>Ajax 是什么?</h3><p>Ajax (Asynchronous JavaScript and XML) 是一种Web应用客户端技术,可以借助客户端脚本(javascript)与服务端应用进行异步通讯(可以有多个线程同时与服务器交互),并且按需获取服务端数据以后,可以进行局部刷新,进而提高数据的响应和渲染速度。</p><h3>Ajax 应用场景?</h3><p>Ajax技术最大的优势就是底层异步,然后局部刷新,进而提高用户体验,这种技术现在在很多项目中都有很好的应用,例如:</p><ul><li>商品系统。</li><li>评价系统。</li><li>地图系统。</li><li>…..</li></ul><p>AJAX可以仅向服务器发送并取回必要的数据,并在客户端采用JavaScript处理来自服务器的响应。这样在服务器和浏览器之间交换的数据大量减少,服务器响应的速度就更快了。但Ajax技术也有劣势,最大劣势是不能直接进行跨域访问。</p><h2>Ajax 快速入门</h2><h3>Ajax 请求响应过程分析</h3><p>传统方式是web请求与响应(客户端要等待响应结果),如图所示:</p><p><img src="/img/bVcIGTn" alt="image.png" title="image.png"></p><p>Ajax方式的请求与响应(关键是客户端不阻塞),如图所示:</p><p><img src="/img/bVcIGTZ" alt="image.png" title="image.png"></p><h3>Ajax 编程步骤及模板代码分析</h3><p>Ajax 编码的基本步骤?(重点是ajax技术的入口-XMLHttpRequest-XHR对象)</p><p>第一步:基于dom事件创建XHR对象<br>第二步:在XHR对象上注册状态监听(监听客户端与服务端的通讯过程)<br>第三步:与服务端建立连接(指定请求方式,请求url,同步还是异步)<br>第四步:发送请求(将请求数据传递服务端)</p><p>Ajax 编码过程的模板代码如下:</p><pre><code>var xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4&&xhr.status==200){
console.log(xhr.responseText)
}
}
xhr.open("GET",url,true);
xhr.send(null);
</code></pre><h3>SpringBoot 项目Ajax技术入门实现</h3><p>第一步:创建项目module,如图所示:</p><p><img src="/img/bVcIGX7" alt="image.png" title="image.png"></p><p>第二步:添加Spring web依赖,代码如下:</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency></code></pre><p>第三步:创建AjaxController处理客户端请求,代码如下:</p><pre><code>package com.cy.pj.ajax.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AjaxController {
@RequestMapping("/doAjaxStart")
public String doAjaxStart(){
return "Response Result Of Ajax Get Request 01 ";
}
}</code></pre><p>第四步:在项目中static目录下,创建一个页面ajax-01.html,代码如下:</p><p>html元素代码如下:</p><pre><code><h1>The Ajax 01 Page</h1>
<fieldset>
<legend>Ajax 异步Get请求</legend>
<button onclick="doAjaxStart()">Ajax Get Request</button>
<span id="result">Data is Loading ...</span>
</fieldset></code></pre><p>javascript 脚本代码如下:</p><pre><code>function doAjaxStart(){
//debugger//客户端断点(此断点生效需要打开控制台)
//1.创建XHR对象(XmlHttpRequest)-Ajax应用的入口对象
let xhr=new XMLHttpRequest();
//2.在XHR对象上注册状态监听(拿到服务端响应结果以后更新到页面result位置)
xhr.onreadystatechange=function(){//事件处理函数(客户端与服务端通讯状态发生变化 时会执行此函数)
//readyState==4表示服务端响应到客户端数据已经接收完成.
if(xhr.readyState==4){
if(xhr.status==200){//status==200表示请求处理过程没问题
document.getElementById("result").innerHTML=
xhr.responseText;
}
}
}
//3.与服务端建立连接(指定请求方式,请求url,异步)
xhr.open("GET","http://localhost/doAjaxStart",true);//true代表异步
//4.向服务端发送请求
xhr.send(null);//get请求send方法内部不传数据或者写一个null
//假如是异步客户端执行完send会继续向下执行.
}</code></pre><p>第五步:启动Tomcat服务并进行访问测试分析.</p><p><img src="/img/bVcIHnI" alt="image.png" title="image.png"></p><p>点击Ajax Get Request 按钮,检测页面数据更新.</p><p>第六步:启动及访问过程中的Bug分析</p><ul><li>点击按钮没反应</li></ul><p><img src="/img/bVcIHzy" alt="image.png" title="image.png"></p><ul><li>访问指定属性的对象不存在</li></ul><p><img src="/img/bVcIHAj" alt="image.png" title="image.png"></p><ul><li>跨域访问</li></ul><p><img src="/img/bVcIHAl" alt="image.png" title="image.png"></p><h2>Ajax 基本业务实现</h2><h3>基本业务描述</h3><p>基于对ajax技术理解,实现ajax方式的Get,Post,Put,Delete等请求的异步处理,如图所示:<br><img src="/img/bVcIItS" alt="image.png" title="image.png"></p><h3>服务端关键代码设计及实现</h3><p>基于业务描述,在AjaxController类中添加相关属性和方法,用于处理客户端的ajax请求.</p><p>添加属性和构造方法,代码如下:</p><pre><code>/**假设这个是用于存储数据的数据库*/
private List<Map<String,String>> dbList=new ArrayList<>();
public AjaxController(){
Map<String,String> map=new HashMap<String,String>();
map.put("id","100");
map.put("city","beijing");
dbList.add(map);
map=new HashMap<String,String>();
map.put("id","101");
map.put("city","shanghai");
dbList.add(map);
}</code></pre><p>添加Ajax请求处理方法,代码如下:</p><pre><code>@GetMapping(path={"/doAjaxGet/{city}","/doAjaxGet")
public List<Map<String,String> doAjaxGet(@PathVariable(required=false) String city){
List<Map<String,String>> list=new ArrayList<>();
for(Map<String,String> map:dbList){
if(map.get("city").contains(city)){
list.add(map);
}
}
return list;
}
@PostMapping("/doAjaxPost")
public String doAjaxPost(@RequestParam Map<String,String> map){
dbList.add(map);
return "save ok";
}
@PostMapping("/doAjaxPostJson")
public String doAjaxPost(@RequestBody Map<String,String> map){
dbList.add(map);
return "save ok";
}
@DeleteMapping("/doAjaxDelete/{id}")
public String doDeleteObject(@PathVariable String id){
//基于迭代器执行删除操作
Iterator<Map<String,String>> it=dbList.iterator();
while(it.hasNext()){
Map<String,String> map=it.next();
if(map.get("id").equals(id)){
it.remove();//基于迭代器执行删除操作
}
}
return "delete ok";
}
@PostMapping("/doAjaxPut")
public String doAjaxPut(@RequestParam Map<String,String> paramMap){
for(Map<String,String> map:dbList){
if(map.get("id").equals(paramsMap.get("id"))){
map.put("city",paramMap.get("city"))
}
}
return "update ok";
}
</code></pre><h3>客户端关键代码设计及实现</h3><p>在static目录下创建ajax-02.html文件,关键代码如下:</p><pre><code><div>
<h1>The Ajax 02 Page</h1>
<button onclick="doAjaxGet()">Do Ajax Get</button>
<button onclick="doAjaxPost()">Do Ajax Post</button>
<button onclick="doAjaxPostJson()">Do Ajax Post Json</button>
<button onclick="doAjaxDelete()">Do Ajax Delete</button>
<button onclick="doAjaxPut()">Do Ajax Put</button>
</div>
</code></pre><p>客户端JavaScript脚本设计,代码如下:</p><ul><li>Get 请求方式,代码如下:</li></ul><pre><code> function doAjaxGet(){
let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
document.getElementById("result").innerHTML=xhr.responseText;
}
}
}
let params=""
xhr.open("GET",`http://localhost/doAjaxGet/${params}`,true);
xhr.send(null);
}</code></pre><ul><li>Post 请求方式,代码如下:</li></ul><pre><code>function doAjaxPost(){
let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
document.getElementById("result").innerHTML=xhr.responseText;
}
}
}
xhr.open("POST","http://localhost/doAjaxPost",true);
//post请求向服务端传递数据,需要设置请求头,必须在open之后
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//发送请求(post请求传递数据,需要将数据写入到send方法内部)
xhr.send("id=102&city=shenzhen");
}</code></pre><pre><code>function doAjaxPost(){
let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
document.getElementById("result").innerHTML=xhr.responseText;
}
}
}
xhr.open("POST","http://localhost/doAjaxPost",true);
//post请求向服务端传递数据,需要设置请求头,必须在open之后
xhr.setRequestHeader("Content-Type", "application/json");
//发送请求(post请求传递数据,需要将数据写入到send方法内部)
let params={id:103,city:"xiongan"};
let paramsStr=JSON.stringify(params);
xhr.send(paramsStr);
}</code></pre><ul><li>Update 请求方式,代码如下:</li></ul><pre><code> function doAjaxPut(){
let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
document.getElementById("result").innerHTML=xhr.responseText;
}
}
}
xhr.open("put","http://localhost/doAjaxPut",true);
//post请求向服务端传递数据,需要设置请求头,必须在open之后
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//发送请求(post请求传递数据,需要将数据写入到send方法内部)
xhr.send("id=101&city=tianjin");
}</code></pre><ul><li>Delete 请求方式,代码如下:</li></ul><pre><code> function doAjaxDelete(){
let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
document.getElementById("result").innerHTML=xhr.responseText;
}
}
}
let params="102";
xhr.open("delete",`http://localhost/doAjaxDelete/${params}`,true);
xhr.send(null);
}
</code></pre><h3>启动服务进行访问测试分析</h3><h2>Ajax 技术进阶实现</h2><h3>Ajax 代码的封装</h3><p>在实际编程过程中我们通常会封装代码共性,提取代码特性.然后来提高代码的可重用性.例如:</p><p>xhr对象的创建</p><pre><code>function createXHR(callback){
//1.create XHR object
let xhr=new XMLHttpRequest();
//2.set onreadystatechange
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
callback(xhr.responseText);
}
}
}
return xhr;
}</code></pre><p>Get请求</p><pre><code>function ajaxGet(url,params,callback){//封装ajax get 请求模板代码
//1.create xhr and set onreadystate change
let xhr=createXHR(callback);
//2.open connection
xhr.open("GET",`${url}/${params}`,true);
//3.send request
xhr.send();
}</code></pre><p>Post请求</p><pre><code>function ajaxPost(url,params,callback){//封装ajax get 请求模板代码
//1.create xhr and set onreadystate change
let xhr=createXHR(callback);
//2.open connection
xhr.open("POST",url,true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
//3.send request
xhr.send(params);
}</code></pre><p>post 请求,传递json数据</p><pre><code>function ajaxPostJSON(url,params,callback){
//1.create xhr and set onreadystate change
let xhr=createXHR(callback);
//2.open connection
xhr.open("POST",url,true);
xhr.setRequestHeader("Content-Type","application/json");
//3.send request
xhr.send(JSON.stringify(params));//将json对象转换为json格式字符串提交到服务端
}</code></pre><p>Put请求</p><pre><code>function ajaxPut(url,params,callback){//封装ajax get 请求模板代码
//1.create xhr and set onreadystate change
let xhr=createXHR(callback);
//2.open connection
xhr.open("put",url,true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
//3.send request
xhr.send(params);
}</code></pre><p>delete请求</p><pre><code>function ajaxDelete(url,params,callback){//封装ajax get 请求模板代码
//1.create xhr and set onreadystate change
let xhr=createXHR(callback);
//2.open connection
xhr.open("delete",`${url}/${params}`,true);
//3.send request
xhr.send();
}</code></pre><p>创建ajax-03.html页面,在页面中分别调用如上函数进行访问测试,关键代码如下:</p><pre><code><div>
<h1>The Ajax 03 Page</h1>
<button onclick="doAjaxGet()">Do Ajax Get</button>
<button onclick="doAjaxPost()">Do Ajax Post</button>
<button onclick="doAjaxPostJson()">Do Ajax Post Json</button>
<button onclick="doAjaxDelete()">Do Ajax Delete</button>
<button onclick="doAjaxPut()">Do Ajax Put</button>
</div>
<div id="result"></div>
<script src="/js/ajax.js"></script>
<script>
//ajax delete request
function doAjaxDelete(){
const url="/doAjaxDelete";
const params="101";
ajaxDelete(url,params,function(result){
alert(result);
})
}
//ajax post put
function doAjaxPut(){
const url="/doAjaxPut";
const params="id=100&city=shenzhen";
ajaxPut(url,params,function(result){
alert(result);
})
}
//ajax post request
function doAjaxPostJson(){
const url="/doAjaxPostJSON";
const params={id:"103",city:"xiongan"};//服务端需要@RequestBody
ajaxPostJSON(url,params,function(result){
alert(result);
})
}
//ajax post request
function doAjaxPost(){
const url="/doAjaxPost";
const params="id=102&city=shenzhen";
ajaxPost(url,params,function(result){
alert(result);
})
}
//ajax get request
function doAjaxGet(){
const url="/doAjaxGet";
const params="";
ajaxGet(url,params,function(result){
document.querySelector("#result").innerHTML=result;
})
}
</script>
</code></pre><h3>Ajax 编程小框架的实现(了解)</h3><p>我们在实际的js编程中经常会以面向对象的方式进行实现,例如ajaxGet函数,如何以对象方式进行调用呢?关键代码分析如下:(如下代码的理解需要具备JS中面向对象的基础知识,假如不熟可暂时跳过)</p><pre><code>(function(){
//定义一个函数,可以将其连接为java中的类
var ajax=function(){}
//在变量ajax指向的类中添加成员,例如doAjaxGet函数,doAjaxPost函数
ajax.prototype={
ajaxGet:function(url,params,callback){
//创建XMLHttpRequest对象,基于此对象发送请求
var xhr=new XMLHttpRequest();
//设置状态监听(监听客户端与服务端通讯的状态)
xhr.onreadystatechange=function(){//回调函数,事件处理函数
if(xhr.readyState==4&&xhr.status==200){
//console.log(xhr.responseText);
callback(xhr.responseText);//jsonStr
}
};
//建立连接(请求方式为Get,请求url,异步还是同步-true表示异步)
xhr.open("GET",url+"?"+params,true);
//发送请求
xhr.send(null);//GET请求send方法不写内容
},
ajaxPost:function(url,params,callback){
//创建XMLHttpRequest对象,基于此对象发送请求
var xhr=new XMLHttpRequest();
//设置状态监听(监听客户端与服务端通讯的状态)
xhr.onreadystatechange=function(){//回调函数,事件处理函数
if(xhr.readyState==4&&xhr.status==200){
//console.log(xhr.responseText);
callback(xhr.responseText);//jsonStr
}
};
//建立连接(请求方式为POST,请求url,异步还是同步-true表示异步)
xhr.open("POST",url,true);
//post请求传参时必须设置此请求头
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
//发送请求
xhr.send(params);//post请求send方法中传递参数
}
}
window.Ajax=new ajax();//构建ajax对象并赋值给变量全局变量Ajax
})()</code></pre><p>此时外界再调用doAjaxGet和doAjaxPost函数时,可以直接通过Ajax对象进行实现。</p><h2>Ajax 技术在Jquery中的应用</h2><h3>Jquery简介</h3><p>jQuery是一个快速、简洁的JavaScript库框架,是一个优秀的JavaScript代码库(或JavaScript框架)。jQuery设计的宗旨是“write Less,Do More”,即倡导写更少的代码,做更多的事情。它封装JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作、事件处理、动画设计和Ajax交互。</p><h3>Jquery中常用ajax函数分析</h3><p>jQuery中基于标准的ajax api 提供了丰富的Ajax函数应用,基于这些函数可以编写少量代码,便可以快速实现Ajax操作。常用函数有:</p><ul><li>ajax(…)</li><li>get(…)</li><li>getJSON(…)</li><li>post(…)</li><li>…</li></ul><p>说明:jquery 中ajax相关函数的语法可参考官网(jquery.com).</p><h3>Jquery中Ajax函数的基本应用实现</h3><p>业务描述<br>构建一个html页面,并通过一些button事件,演示jquery中相关ajax函数的基本应用,如图所示:<br><img src="/img/bVcIKGd" alt="image.png" title="image.png"></p><p>关键步骤及代码设计如下:<br>第一步:在static目录下添加jquery-ajax-01.html页面.<br>第二步:在页面上添加关键的html元素,代码如下:</p><pre><code><h1>The Jquery Ajax 01 Page</h1>
<button onclick="doAjax()">$.ajax(...)</button>
<button onclick="doAjaxPost()">$.post(...)</button>
<button onclick="doAjaxGet()">$.get(...)</button>
<button onclick="doAjaxLoad()">$("..").load(...)</button>
<div id="result"></div>
<script src="/js/jquery.min.js"></script></code></pre><p>第三步:添加button事件对应的事件处理函数,代码如下:</p><ul><li>ajax 函数应用,代码如下:</li></ul><pre><code>function doAjax(){
let url="http://localhost/doAjaxGet";
let params="";
//这里的$代表jquery对象
//ajax({})这是jquery对象中的一个ajax函数(封装了ajax操作的基本步骤)
$.ajax({
type:"GET",//省略type,默认就是get请求
url:url,
data:params,
async:true,//默认为true,表示异步
success:function(result){//result为服务端响应的结果
console.log(result);//ajax函数内部将服务端返回的json串转换为了js对象
}//success函数会在服务端响应状态status==200,readyState==4的时候执行.
});
}</code></pre><ul><li>post 函数应用,代码如下</li></ul><pre><code>function doAjaxPost(){
let url="http://localhost/doAjaxPost";
let params="id=101&name=Computer&remark=Computer...";
$.post(url,params,function(result){
$("#result").html(result);
});</code></pre><ul><li>get函数应用,代码如下:</li></ul><pre><code>function doAjaxGet(){
let url="http://localhost/doAjaxGet";
let params="";
$.get(url,params,function(result){
$("#result").html(result);
},"text");
}</code></pre><ul><li>load 函数应用,代码如下:</li></ul><pre><code>function doAjaxLoad(){
let url="http://localhost/doAjaxGet";
$("#result").get(url,function(){
console.log("load complete")
});
}</code></pre><h2>总结(Summary)</h2><p>本章主要介绍了Ajax是什么、应用场景、客户端与服务端的通讯模型、ajax编程的基本步骤、封装过程以及ajax技术在一些JS框架中的应用等,并且重点分析了在ajax编码过程中的一些调试技巧。</p>
07-SpringBoot+MyBatis+Spring 技术整合实现商品品牌模块的CRUD操作
https://segmentfault.com/a/1190000037732462
2020-11-05T12:14:17+08:00
2020-11-05T12:14:17+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
121
<h2>业务描述</h2><h3>需求描述</h3><p>任何一个电商系统中都有一个商品子系统,而与商品相关联都会有一个品牌信息管理,在当前业务系统设计中我们就是要对商品品牌信息的管理进行设计和实现.</p><h3>业务架构分析</h3><p>在品牌(Brand)信息管理中就是要实现对商品品牌信息的添加,修改,查询,删除等业务,如图所示:<br><img src="/img/bVcIttH" alt="image.png" title="image.png"></p><h3>业务基本原型设计</h3><p>基于品牌业务描述,对品牌模块的业务原型进行分析和设计,如图所示:</p><ul><li>品牌列表页面</li></ul><p><img src="/img/bVcIBpW" alt="image.png" title="image.png"></p><ul><li>品牌编辑页面</li></ul><p><img src="/img/bVcIBqg" alt="image.png" title="image.png"></p><h3>项目技术架构分析及设计</h3><p>在品牌管理模块实现过程,我们采用典型的C/S架构进行实现.客户端我们基于浏览器进行实现,服务端采用tomcat,数据库使用MySQL.具体应用层基于MVC分层架构进行实现.</p><h3>项目技术栈应用分析及选型</h3><ul><li>客户端技术:html,css,javascript,bootstrap</li><li>服务端技术:hikaricp,mybatis,spring,springboot,thymeleaf</li><li>数据库技术:mysql,sql</li><li>开发工具集:jdk1.8,maven3.6.3,idea2020.2</li></ul><h3>项目品牌模块核心API分析与设计</h3><p>基于分层架构设计思想,现对品牌API进行设计,如图所示:</p><p><img src="/img/bVcItlw" alt="image.png" title="image.png"></p><h2>数据库及表的分析与设计</h2><h3>设计并创建数据库</h3><p>假如数据库已存在,则先删除数据库,代码如下:</p><pre><code>drop database if exists dbbrand;</code></pre><p>创建新的数据库,代码如下:</p><pre><code>create database dbbrand default character set utf8;</code></pre><h3>设计并创建品牌(Brand)表</h3><p>打开数据库,语句如下:</p><pre><code>use dbbrand;</code></pre><p>在dbbrand数据库中创建品牌表.</p><pre><code>create table tb_brand(
id bigint primary key auto_increment,
name varchar(100) not null,
remark text,
createdTime datetime not null
)engine=InnoDB;</code></pre><h3>基于SQL脚本执行数据库初始化</h3><p>将数据库设计脚本写到brand.sql中,然后按如下步骤执行:</p><p>打开mysql自带客户端,登录mysql,指令如下:</p><pre><code>mysql -uroot -proot</code></pre><p>设置客户端编码,指令如下:</p><pre><code>set names utf8;</code></pre><p>执行sql脚本,指令如下:</p><pre><code>source d:/brand.sql</code></pre><p>脚本执行成功以后,在客户端查询数据之前,先执行如下语句:</p><pre><code>set names gbk;</code></pre><h2>项目环境初始化</h2><h3>准备操作</h3><p>1)JDK 1.8<br>2)Maven 3.6.3<br>3)IDEA 2020.2<br>4)MySQL 5.7+</p><h3>创建项目Module</h3><p>打开idea,然后基于设计,创建项目module,如图所示:</p><p><img src="/img/bVcIt53" alt="image.png" title="image.png"></p><h3>添加项目Module依赖</h3><ul><li>MySQL 驱动</li></ul><pre><code><dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency></code></pre><ul><li>Srping Jdbc 提供了HikariCP连接池</li></ul><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency></code></pre><ul><li>MyBatis 资源</li></ul><pre><code><dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency></code></pre><ul><li>Spring Web 依赖 (内置一个tomcat服务)</li></ul><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency></code></pre><ul><li>Thymeleaf 依赖 (html模板引擎)</li></ul><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency></code></pre><h3>项目Module基础配置</h3><p>打开项目Module配置文件application.properties,并添加如下内容:</p><pre><code>#spring server
server.port=80
# spring datasource
spring.datasource.url=jdbc:mysql:///dbbrand?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
# spring mybatis
mybatis.mapper-locations=classpath:/mapper/*/*.xml
# spring log
logging.level.com.cy=debug
#spring thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html</code></pre><h3>启动项目进行初步环境测试分析</h3><ul><li>端口号被占用</li></ul><p><img src="/img/bVcIt3a" alt="image.png" title="image.png"></p><ul><li>数据库连接错误</li></ul><p><img src="/img/bVcIvMB" alt="image.png" title="image.png"></p><h2>品牌数据的查询及呈现</h2><h3>业务描述</h3><p>将数据库中的品牌信息查询出来,然后在客户端基于html技术进行呈现,如图所示:<br><img src="/img/bVcIBpW" alt="image.png" title="image.png"></p><h3>服务端品牌查询时序设计</h3><p>基于查询请求,进行访问时序设计,如图所示:<br><img src="/img/bVcItuU" alt="image.png" title="image.png"></p><h3>领域对象(POJO)设计及实现</h3><p>设置Brand对象,基于此对象封装从数据库查询到的品牌信息,代码如下:</p><pre><code>package com.cy.pj.brand.pojo;
import java.util.Date;
public class Brand {
private Integer id;
private String name;
private String remark;
private Date createdTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", name='" + name + ''' +
", remark='" + remark + ''' +
", createdTime=" + createdTime +
'}';
}
}</code></pre><h3>数据逻辑对象(DAO)查询方法设计及实现</h3><p>设计用于访问Brand数据的数据访问对象及方法,其关键步骤如下:</p><p>第一步:定义BrandDao接口,代码如下:</p><pre><code>package com.cy.pj.brand.dao;
@Mapper
public interface BrandDao{
}</code></pre><p>第二步:在BrandDao中定义品牌查询方法,代码如下:</p><pre><code>List<Brand> findBrands(String name);</code></pre><p>第三步:基于查询方法定义SQL映射.<br>在resources目录中创建mapper/brand目录,并在目录中添加BrandMapper.xml文件,关键代码如下:</p><pre><code><?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.cy.pj.brand.dao.BrandDao">
<select id="findBrands" resultType="com.cy.pj.brand.pojo.Brand">
select id,name,remark,createdTime
from tb_brand
<if test="name!=null and name!=''">
where name like concat("%",#{name},"%")
</if>
</select>
</mapper></code></pre><p>其中:concat为mysql中提供的字符串连接函数.</p><p>第四步:对数据层的查询方法进行单元测试,代码如下:</p><pre><code>package com.cy.pj.brand.dao;
@SpringBootTests
public class BrandDaoTests{
@Autowired
private BrandDao brandDao;
@Test
void testFindBrands(){
List<Brand> list=brandDao.findBrands("TCL");
for(Brand b:list){
System.out.println(b);
}
}
}
</code></pre><p>第五步:测试过程中的BUG分析?</p><ul><li>BindingException,如图所示:</li></ul><p><img src="/img/bVcIvM5" alt="image.png" title="image.png"></p><ul><li>ExecutorException,如图所示:</li></ul><p><img src="/img/bVcIvNK" alt="image.png" title="image.png"></p><h3>业务逻辑对象(Service)查询方法设计及实现</h3><p>业务逻辑对象负责模块的具体业务处理,例如参数校验,事务控制,权限控制,日志记录等.</p><p>第一步:定义业务接口</p><pre><code>package com.cy.pj.brand.service;
public interface BrandService{
}
</code></pre><p>第二步:在BrandService接口中添加品牌查询方法</p><pre><code>List<Brand> findBrands(String name);</code></pre><p>第三步:定义BrandService接口实现类BrandServiceImpl.</p><pre><code>package com.cy.pj.brand.service.impl;
@Service
public class BrandServiceImpl implements BrandService{
private static final Logger log=
LoggerFactory.getLogger(BrandServiceImpl.class);
@Autowired
private BrandDao brandDao;
public List<Brand> findBrands(String name){
long t1=System.currentTimeMillis();
List<Brand> list=brandDao.findBrands(name);
long t2=System.currentTimeMillis();
log.info("findBrands->time->{}",(t2-t1));
return list;
}
}
</code></pre><p>第三步:定义BrandService接口方法的单元测试类,并业务进行测试分析</p><pre><code>package com.cy.pj.brand.service;
@SpringBootTest
public class BrandServiceTests{
@Autowired
private BrandService brandService;
@Test
void testFindBrands(){
List<Brand> list=brandService.findBrands();
for(Brand b:list){
System.out.println(b);
}
}
}</code></pre><p>第四步:测试过程中的Bug分析</p><ul><li>NoSuchBeanDefinition,如图所示:</li></ul><p><img src="/img/bVcIvOi" alt="image.png" title="image.png"></p><ul><li>NullPointerException,如图所示:</li></ul><p><img src="/img/bVcIvOA" alt="image.png" title="image.png"></p><h3>控制逻辑对象(Controller)查询方法设计及实现</h3><p>在控制逻辑对象中主要是负责请求和响应逻辑控制,例如请求url映射,参数映射,请求方式,结果集的封装,解析,响应的设计等.</p><p>第一步:定义Controller类</p><pre><code>package com.cy.pj.brand.controller;
@Controller
public class BrandController{
@Autowired
private BrandService brandService;
}</code></pre><p>第二步:在Controller添加处理查询请求的方法</p><pre><code>@GetMapping(value={"/brand/doFindBrands/{name}","/brand/doFindBrands"})
public String doFindBrands(@PathVariable(required = false) String name, Model model){
List<Brand> list=brandService.findBrands(name);
model.addAttribute("list", list);
return "brand/brand";//第一个brand为目录,第二brand为view name
}
</code></pre><p>其中,<br>1)@GetMapping描述方法时,表示这个方法只能处理Get请求,注解内部的value属性可以指定多个url.<br>2)@PathVariable 用于描述方法参数,表示方法参数的值可以来自url中{}内部的变量值,required=false表示参数可以不传值.</p><h3>客户端品牌列表页面设计及实现</h3><p>在项目的templates目录下创建brand目录并添加brand.html页面,其关键代码如下:</p><pre><code><table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>createdTime</th>
</tr>
</thead>
<tbody>
<tr th:each="brand: ${list}">
<td th:text="${brand.id}">10</td>
<td th:text="${brand.name}">AAA</td>
<td th:text="${#dates.format(brand.createdTime, 'yyyy/MM/dd
HH:mm')}">2020/10/11</td>
</tr>
</tbody>
</table></code></pre><p>其中:<br>1)${}为thymeleaf为中的EL表达式,用于从服务端model中获取数据<br>2)th:each为thymeleaf定义的自定义标签属性,用于迭代数据.<br>3)th:text为thymeleaf定义的自定义标签属性,用于设置文本内容.</p><h3>启动服务进行访问测试并对结果进行分析</h3><p>启动服务,打开浏览器输入指定url,进行访问,其数据呈现过程,如图所示:<br><img src="/img/bVcIBqq" alt="image.png" title="image.png"></p><h3>启动及运行过程中的Bug分析及解决方案</h3><ul><li>页面元素解析异常,如图所示:</li></ul><p><img src="/img/bVcIvHp" alt="image.png" title="image.png"></p><ul><li>模板页面找不到,如图所示:</li></ul><p><img src="/img/bVcIvKF" alt="image.png" title="image.png"></p><h2>品牌模块删除业务分析及实现</h2><h3>业务描述</h3><p>在品牌列表页面中,点击当前行记录后面的删除按钮,基于当前行的记录id执行品牌删除操作,删除成功以后,重新刷新页面.如图所示:</p><p><img src="/img/bVcIBp4" alt="image.png" title="image.png"></p><h3>业务时序分析与设计</h3><p>客户端向服务端发起删除请求,服务器端的处理时序如下:</p><p><img src="/img/bVcIxqh" alt="image.png" title="image.png"></p><h3>数据逻辑对象(DAO)中删除方法设计及实现</h3><p>基于业务,在BrandDao接口中添加删除方法,代码如下:</p><pre><code>int deleteById(Integer id);</code></pre><p>基于删除方法,定义SQL映射(本次直接以注解方式进行定义),代码如下:</p><pre><code>@Delete("delete from tb_brand where id=#{id}")
int deleteById(Integer id);</code></pre><p>在BrandDaoTests单元测试类中添加单元测试方法,对删除操作进行测试,关键代码如下</p><pre><code>@Test
void testDeleteById(){
int rows=brandDao.deleteById(10);
System.out.println("rows="+rows);
}</code></pre><h3>业务逻辑对象(Service)中删除方法设计及实现</h3><p>在业务逻辑对象方法中,要处理删除操作需要的一些业务逻辑(后续有参数校验,权限控制,....).</p><p>第一步:在BrandService接口中添加,品牌删除的业务方法,代码如下:</p><pre><code>int deleteById(Integer id);</code></pre><p>第二步:在BrandServiceImpl类中添加删除业务的具体实现,代码如下:</p><pre><code>public int deleteById(Integer id){
//1.参数校验
//2.执行删除业务
int rows=brandDao.deleteById(id);
//3.校验结果并返回
return rows;
}
</code></pre><p>第三步:在BrandServiceTests类中添加单元测试方法,对其删除业务做测试?</p><pre><code>@Test
void testDeleteById(){
int rows=brandService.deleteById(10);
System.out.println("row="+row);
}</code></pre><p>第四步:测试过程中的Bug分析?</p><h3>控制逻辑对象(Controller)中删除方法设计及实现</h3><p>在控制层对象中定义处理删除请求的方法,具体代码如下:</p><pre><code>@GetMapping("/brand/doDeleteById/{id}")
public String doDeleteById(@PathVariable Integer id,Model model){
brandService.deleteById(id);
List<Brand> list=brandService.findBrands();
model.addAttribute("list",list);
return "brand/brand";
}
</code></pre><h3>客户端删除按钮事件定义及处理</h3><p>在tbody的tr中添加一列,代码如下:</p><pre><code><td>
<button type="button" th:onclick="doDeleteById([[${brand.id}]])">delete</button>
</td></code></pre><p>定义javascript函数,处理删除事件,代码如下:</p><pre><code>function doDeleteById(id){
//给出提示信息
if(!confirm("您确认删除吗"))return;//confirm为浏览器中window对象的函数
//执行删除业务
location.href=`http://localhost/brand/doDeleteById/${id}`;
}</code></pre><h2>品牌模块添加业务分析及实现</h2><h3>业务描述</h3><p>在列表页面上,设计添加按钮,当点击添加按钮时,跳转到添加页面,然后在添加页面上数据品牌信息,点击Save按钮就数据提交到服务端进行保存.</p><p><img src="/img/bVcIBqe" alt="image.png" title="image.png"></p><p><img src="/img/bVcIBqh" alt="image.png" title="image.png"></p><h3>添加时序分析及设计</h3><p><img src="/img/bVcIyUW" alt="image.png" title="image.png"></p><h3>数据逻辑对象(Dao)中方法设计及实现</h3><p>第一步:在BrandDao中添加用于保存品牌信息的方法,代码如下:</p><pre><code> int insertBrand(Brand brand);</code></pre><p>第二步:在BrandMapper中添加品牌保存操作对应的sql映射,代码如下:</p><pre><code><insert id="insertBrand">
insert into tb_brand
(name,remark,createdTime)
values
(#{name},#{remark},now())
</insert></code></pre><h3>业务逻辑对象(Service)中方法设计及实现</h3><p>第一步:在BrandService业务接口中中定义用于保存品牌信息的方法,代码如下:</p><pre><code>int saveBrand(Brand brand); </code></pre><p>第二步:在BrandServiceImpl业务实现类中添加保存品牌信息的具体实现,代码如下:</p><pre><code>public int saveBrand(Brand brand){
int rows=brandDao.insertBrand(brand);
return rows;
}</code></pre><h3>控制逻辑对象(Controller)中方法设计及实现</h3><p>第一步:在BrandController中添加用于处理请求添加页面的方法,代码如下:</p><pre><code>@GetMapping("/brand/doAddUI")
public String doAddUI(){
return "brand/brand-add";
}</code></pre><p>第二步:在BrandController添加用于处理添加品牌信息页面的方法,代码如下:</p><pre><code>@PostMapping("/brand/doSaveBrand")
public String doSaveBrand(Brand brand,Model model){
System.out.println("save.brand="+brand);
brandService.saveBrand(brand);
List<Brand> list=brandService.findBrands(null);
model.addAttribute("list",list);
return "brand/brand";
}</code></pre><h3>品牌添加操作客户端业务分析、设计及实现.</h3><p>第一步:设计品牌添加页面brand-add.html,代码如下</p><pre><code><!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/bootstrap/css/bootstrap.css">
</head>
<body>
<div class="container">
<h1>The Brand Add Page</h1>
<form th:action="@{/brand/doSaveBrand}" method="post">
<div class="form-group">
<label for="nameId">Name</label>
<input type="text" class="form-control" name="name" id="nameId" placeholder="Brand Name">
</div>
<div class="form-group">
<label for="remarkId">Remark</label>
<textarea class="form-control" rows="5" cols="100" name="remark" id="remarkId">
</textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</body>
</html></code></pre><p>第二步:在品牌列表brand.html页面,设计添加按钮,代码如下:</p><pre><code><button type="button" onclick="doAddUI()" class="btn btn-primary btn-sm">Add Brand</button></code></pre><p>第三步:点击添加按钮时,加载品牌添加页面,事件处理函数如下:</p><pre><code>function doAddUI(){
location.href="/brand/doAddUI";
}</code></pre><h3>启动服务进行访问测试分析</h3><p><img src="/img/bVcIBqn" alt="image.png" title="image.png"></p><p><img src="/img/bVcIBqp" alt="image.png" title="image.png"></p><h3>启动及运行过程中的BUG分析</h3><ul><li>客户端提交到服务端的数据拿不到?</li></ul><p><img src="/img/bVcIDce" alt="image.png" title="image.png"></p><h2>品牌模块修改业务分析及实现</h2><h3>业务描述</h3><p>在品牌列表页面,点击当前行的修改按钮,先基于id查询当前行记录,并将记录呈现在编辑页面,如图所示:</p><p><img src="/img/bVcIBNk" alt="image.png" title="image.png"></p><p><img src="/img/bVcIBNx" alt="image.png" title="image.png"></p><h3>业务时序分析与设计</h3><p>基于id查询品牌信息并呈现在页面上,其时序分析如图所示:</p><p><img src="/img/bVcIBOi" alt="image.png" title="image.png"></p><p>在品牌编辑页面,编辑数据,点击save按钮保存更新,其时序如图所示:<br><img src="/img/bVcIBOL" alt="image.png" title="image.png"></p><h3>数据逻辑对象(Dao)中方法设计与实现</h3><p>在BrandDao中添加基于id查询品牌信息的方法及SQL映射,代码如下:</p><pre><code>@Select("select * from tb_brand where id=#{id}")
Brand findById(Integer id);</code></pre><p>在BrandDao中添加基于id执行品牌更新的方法及SQL映射,代码如下:</p><pre><code> @Update("update tb_brand set name=#{name},remark=#{remark} where id=#{id}")
int updateBrand(Brand Brand);</code></pre><h3>业务逻辑对象(Service)中方法设计与实现</h3><p>在BrandService 中添加基于id查询品牌信息和更新品牌信息的方法,代码如下:</p><pre><code>Brand findById(Integer id);
int updateBrand(Brand brand);</code></pre><p>在BrandServiceImpl中基于id查询品牌信息和更新品牌信息的方法,代码如下:</p><pre><code> @Override
public Brand findById(Integer id) {
//.....
return brandDao.findById(id);
}</code></pre><pre><code> @Override
public int updateBrand(Brand brand) {
return brandDao.updateBrand(brand);
}</code></pre><h3>控制逻辑对象(Controller)中方法设计与实现</h3><p>在BrandController中添加基于id查询品牌信息的方法,代码如下:</p><pre><code> @RequestMapping("/brand/doFindById/{id}")
public String doFindById(@PathVariable Integer id,Model model) {
Brand brand=brandService.findById(id);
model.addAttribute("brand",brand);
return "/brand/brand-update";
}</code></pre><p>在BrandController中添加更新品牌信息的方法,代码如下:</p><pre><code> @RequestMapping("/brand/doUpdateBrand")
public String doUpdateBrand(Brand brand,Model model) {
brandService.updateBrand(brand);
List<Brand> list=brandService.findBrands(null);
model.addAttribute("list",list);
return "brand/brand";
}</code></pre><h3>客户端品牌编辑页面设计与实现</h3><p>第一步:设计品牌修改页面brand-update.html,代码如下</p><pre><code><!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/bootstrap/css/bootstrap.css">
</head>
<body>
<div class="container">
<h1>The Brand Update Page</h1>
<form th:action="@{/brand/doUpdateBrand}" method="post">
<input type="hidden" name="id" th:value="${brand.id}">
<div class="form-group">
<label for="nameId">Name</label>
<input type="text" class="form-control" name="name" id="nameId" th:value="${brand.name}" placeholder="Brand Name">
</div>
<div class="form-group">
<label for="remarkId">Remark</label>
<textarea class="form-control" rows="5" cols="100" name="remark" th:text="${brand.remark}" id="remarkId">
</textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</body>
</html></code></pre><h3>启动Tomcat服务进行访问测试分析</h3><p>启动服务,先进入品牌列表页面,然后点击修改按钮如图所示:<br><img src="/img/bVcIBNk" alt="image.png" title="image.png"><br>此时,进入品牌编辑页面,如图所示:<br><img src="/img/bVcIBNx" alt="image.png" title="image.png"><br>在品牌编辑页面,编辑数据以后,点击save按钮,执行更新操作。</p><h3>启动及运行过程中的BUG分析</h3><ul><li>405 异常</li></ul><p><img src="/img/bVcIC8R" alt="image.png" title="image.png"></p><ul><li>呈现的数据不正确</li></ul><p><img src="/img/bVcIC82" alt="image.png" title="image.png"></p><ul><li>页面元素解析异常</li></ul><p><img src="/img/bVcIC9h" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本章节,主要基于学过的springboot,Hikaricp,MyBatis,Spring,Thymeleaf等技术,对商品品牌模块做了具体实现。重点掌握其基本设计及实现过程。</p>
15-基于Spring,MyBatis,SpringBoot,Vue技术实现购物券系统的增删改查操作。
https://segmentfault.com/a/1190000037522363
2020-10-19T12:53:25+08:00
2020-10-19T12:53:25+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
52
<h2>业务描述</h2><p>基于Spring,MyBatis,SpringBoot,VUE技术实现购物券的增删改查操作。</p><h2>项目环境初始化</h2><h3>准备工作</h3><pre><code>1. MySQL(5.7)
2. JDK (1.8)
3. Maven (3.6.3)
4. IDEA(2020.2)</code></pre><h3>数据库初始化</h3><p>打开mysql控制台,然后按如下步骤执行dbretail.sql文件。 <br>第一步:登录mysql。</p><pre><code>mysql –uroot –proot</code></pre><p>第二步:设置控制台编码方式。</p><pre><code>set names utf8;</code></pre><p>第三步:执行dbretail.sql文件(切记不要打开文件复制到mysql客户端运行)。</p><pre><code>source d:/dbretail.sql</code></pre><p>其中dbvoucher.sql文件内容如下:</p><pre><code>drop database if exists dbretail;
create database dbretail default character set utf8;
use dbretail;
CREATE TABLE tb_voucher(
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`deno` DECIMAL(10,2) UNSIGNED NOT NULL COMMENT '面值',
`requirement` DECIMAL(10,2) UNSIGNED NOT NULL COMMENT '订单多少钱可以使用',
`startTime` DATETIME COMMENT '起始日期',
`endTime` DATETIME COMMENT '截至日期',
`maxNum` INT COMMENT '代金卷发放最大数量',
`remark` varchar(200) COMMENT '备注',
`createdTime` DATETIME COMMENT '创建日期'
) COMMENT='购物券表';
insert into tb_voucher values (null,100,1000,'2020/05/07','2020/06/07',200,'Remark',now());
insert into tb_voucher values (null,80,800,'2020/05/07','2020/06/07',300,'Remark',now());
insert into tb_voucher values (null,50,500,'2020/05/07','2020/06/07',400,'Remark',now());
insert into tb_voucher values (null,30,300,'2020/05/07','2020/06/07',500,'Remark',now());
insert into tb_voucher values (null,10,100,'2020/05/07','2020/06/07',600,'Remark',now());</code></pre><h3>创建项目并添加依赖</h3><h4>基于IDEA创建</h4><p>第一步:创建Empty项目并设置基本信息(假如Empty项目已经存在则无需创建)</p><p><img src="/img/bVcHzT0" alt="image.png" title="image.png"></p><p><img src="/img/bVcHzUg" alt="image.png" title="image.png"></p><p>第二步:基于start.spring.io 创建项目Module,并设置基本信息。</p><p><img src="/img/bVceWXJ" alt="image.png" title="image.png"></p><p><img src="/img/bVcHzU3" alt="image.png" title="image.png"></p><p>第三步:创建项目module时指定项目核心依赖</p><p><img src="/img/bVcHzVf" alt="image.png" title="image.png"></p><p>第四步:继续完成module的创建并分析其结构</p><p><img src="/img/bVcHzVv" alt="image.png" title="image.png"></p><h3>项目配置文件内容初始化</h3><pre><code>#server
server.port=80
#spring datasource
spring.datasource.url=jdbc:mysql:///dbvoucher?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#spring mybatis
mybatis.mapper-locations=classpath:/mapper/*/*.xml
#spring logging
logging.level.com.cy.retail=debug
</code></pre><h3>项目API架构设计</h3><p>其API架构设计,如图所示:</p><p><img src="/img/bVcHzYZ" alt="image.png" title="image.png"></p><h2>商品查询业务实现</h2><h3>业务描述</h3><p>从数据库查询购物卷信息,并将购物卷信息呈现在页面上,如图所示:</p><p><img src="/img/bVcHzZl" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>查询所有购物券信息,其业务时序分析,如图所示:<br><img src="/img/bVcHz1Q" alt="image.png" title="image.png"></p><h3>Pojo类定义</h3><p>定义Voucher对象,用于封装从数据库查询到的购物券信息。</p><pre><code>package com.cy.retail.voucher.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serializable;
import java.util.Date;
/**购物卷*/
public class Voucher implements Serializable {
private static final long serialVersionUID = -8863293149012534747L;
private Integer id;
/**面值*/
private Integer deno;
/**条件(多少钱可以使用)*/
private Integer requirement;
/**起始时间*/
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
private Date startTime;
/**结束时间*/
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
private Date endTime;
/**代金卷发放最大数量*/
private Integer maxNum;
/**备注*/
private String remark;
/**创建时间*/
private Date createdTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getDeno() {
return deno;
}
public void setDeno(Integer deno) {
this.deno = deno;
}
public Integer getRequirement() {
return requirement;
}
public void setRequirement(Integer requirement) {
this.requirement = requirement;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Integer getMaxNum() {
return maxNum;
}
public void setMaxNum(Integer maxNum) {
this.maxNum = maxNum;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return "Voucher{" +
"id=" + id +
", deno=" + deno +
", requirement=" + requirement +
", startTime=" + startTime +
", endTime=" + endTime +
", maxNum=" + maxNum +
", remark='" + remark + ''' +
", createdTime=" + createdTime +
'}';
}
}</code></pre><h3>Dao接口方法及映射定义</h3><p>在GoodsDao接口中定义商品查询方法以及SQL映射,基于此方法及SQL映射获取所有商品信息。代码如下:</p><pre><code>package com.cy.retail.voucher.dao;
import com.cy.retail.voucher.pojo.Voucher;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface VoucherDao {
@Select("select count(*) from tb_voucher")
int getRowCount();
@Select("select * from tb_voucher order by createdTime desc limit #{startIndex},#{pageSize}")
List<Voucher> findPageObjects(Integer startIndex, Integer pageSize);
}
</code></pre><p>编写单元测试类进行测试分析:</p><pre><code>package com.cy.retail.voucher.dao;
import com.cy.retail.voucher.pojo.Voucher;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class VoucherDaoTests {
@Autowired
private VoucherDao voucherDao;
@Test
void testGetRowCount(){
int rowCount=voucherDao.getRowCount();
System.out.println("rowCount="+rowCount);
}
@Test
void testFindPageObjects(){
List<Voucher> list=voucherDao.findPageObjects(0,3);
list.forEach(item-> System.out.println(item));
}
}</code></pre><h3>Service接口方法定义及实现</h3><p>业务逻辑层分页逻辑封装对象定义,代码如下:</p><pre><code>package com.cy.retail.common.pojo;
import java.io.Serializable;
import java.util.List;
public class PageObject<T> implements Serializable {
/**总记录数*/
private Integer rowCount;
/**当前页记录*/
private List<T> records;
/**总页数(计算出来)*/
private Integer pageCount;
/**当前页码值*/
private Integer pageCurrent;
/**页面大小(每页最多显示多少条记录)*/
private Integer pageSize;
public PageObject(Integer rowCount, List<T> records, Integer pageCurrent, Integer pageSize) {
this.rowCount = rowCount;
this.records = records;
this.pageCurrent = pageCurrent;
this.pageSize = pageSize;
this.pageCount=rowCount/pageSize;
if(this.rowCount%this.pageSize!=0)this.pageCount++;
}
public Integer getRowCount() {
return rowCount;
}
public void setRowCount(Integer rowCount) {
this.rowCount = rowCount;
}
public List<T> getRecords() {
return records;
}
public void setRecords(List<T> records) {
this.records = records;
}
public Integer getPageCount() {
return pageCount;
}
public void setPageCount(Integer pageCount) {
this.pageCount = pageCount;
}
public Integer getPageCurrent() {
return pageCurrent;
}
public void setPageCurrent(Integer pageCurrent) {
this.pageCurrent = pageCurrent;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
@Override
public String toString() {
return "PageObject{" +
"rowCount=" + rowCount +
", records=" + records +
", pageCount=" + pageCount +
", pageCurrent=" + pageCurrent +
", pageSize=" + pageSize +
'}';
}
}</code></pre><p>VoucherService接口及查询方法定义</p><pre><code>package com.cy.retail.voucher.service;
import com.cy.retail.common.pojo.PageObject;
import com.cy.retail.voucher.pojo.Voucher;
public interface VoucherService {
/**
* 分页查询Voucher信息
* @param pageCurrent
* @param pageSize
* @return
*/ PageObject<Voucher> findPageObjects(Integer pageCurrent,Integer pageSize);
}</code></pre><p>VoucherService接口实现类VoucherServiceImpl定义及方法实现</p><pre><code>package com.cy.retail.voucher.service.impl;
import com.cy.retail.common.pojo.PageObject;
import com.cy.retail.voucher.dao.VoucherDao;
import com.cy.retail.voucher.pojo.Voucher;
import com.cy.retail.voucher.service.VoucherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class VoucherServiceImpl implements VoucherService {
@Autowired
private VoucherDao voucherDao;
@Override
public int deleteById(Integer id) {
return voucherDao.deleteById(id);
}
@Override
public PageObject<Voucher> findPageObjects(Integer pageCurrent, Integer pageSize) {
int rowCount=voucherDao.getRowCount();
int startIndex=(pageCurrent-1)*pageSize;
List<Voucher> records=voucherDao.findPageObjects(startIndex,pageSize);
return new PageObject<>(rowCount,records,pageCurrent,pageSize);
}
}</code></pre><p>编写单元测试类进行测试分析</p><pre><code>package com.cy.retail.voucher.service;
import com.cy.retail.common.pojo.PageObject;
import com.cy.retail.voucher.dao.VoucherDao;
import com.cy.retail.voucher.pojo.Voucher;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class VoucherServiceTests {
@Autowired
private VoucherService voucherService;
@Test
void testFindPageObjects(){
PageObject<Voucher> pageObject=
voucherService.findPageObjects(1,3);
System.out.println(pageObject);
}
}</code></pre><h3>Controller对象方法定义及实现</h3><p>控制逻辑层响应数据标准对象设计及实现,代码如下:</p><pre><code>package com.cy.retail.common.pojo;
import java.io.Serializable;
public class ResponseResult implements Serializable {
private static final long serialVersionUID = -4971076199594828397L;
/**状态:1 ok,0 error*/
private Integer state=1;
/**状态信息*/
private String message="ok";
/**业务层返回的正确响应数据*/
private Object data;
public ResponseResult(){}
public ResponseResult(String message){ //new JsonResult("update ok");
this.message=message;
}
public ResponseResult(Object data){
this.data=data;
}
public ResponseResult(Throwable e){
this.state=0;
this.message=e.getMessage();
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}</code></pre><p>定义VoucherController类,并添加doFindPageObjects方法,代码如下:</p><pre><code>ackage com.cy.retail.voucher.controller;
import com.cy.retail.common.pojo.PageObject;
import com.cy.retail.common.pojo.ResponseResult;
import com.cy.retail.voucher.service.VoucherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class VoucherController {
@Autowired
private VoucherService voucherService;
@GetMapping("/voucher/doFindPageObjects/{pageCurrent}/{pageSize}")
public ResponseResult doFindPageObjects(@PathVariable Integer pageCurrent,@PathVariable Integer pageSize){
return new ResponseResult(voucherService.findPageObjects(pageCurrent,pageSize));
}
}</code></pre><p>控制层统一异常处理类设计及实现,关键代码如下:</p><pre><code>package com.cy.retail.common.web;
import com.cy.retail.common.pojo.ResponseResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(RuntimeException.class)
public ResponseResult doHandleRuntimeException(RuntimeException e){
e.printStackTrace();//控制台输出
log.error("exception msg {}"+e.getMessage());
return new ResponseResult(e);
}
}</code></pre><h3>Voucher页面设计及实现</h3><p>在static目录中添加voucher.html页面,代码如下:</p><pre><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="container">
<h1>The Voucher Page</h1>
<table class="table">
<thead>
<tr>
<th>序号</th>
<th>面值</th>
<th>可用条件</th>
<th>起始日期</th>
<th>截止日期</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(v,i) of vouchers" :key="i">
<td>{{i+1}}</td>
<td>{{v.deno}}</td>
<td>{{v.requirement}}</td>
<td>{{v.startTime}}</td>
<td>{{v.endTime}}</td>
<td>{{v.maxNum}}</td>
<td><button class="btn btn-danger btn-xs">delete</button></td>
</tr>
</tbody>
</table>
<div id="pageId" class="box-footer clearfix" dm="100">
<ul class="pagination pagination-sm no-margin pull-right">
<li><a class="first" @click="doJumpToPage">首页</a></li>
<li><a class="pre" @click="doJumpToPage">上一页</a></li>
<li><a class="next" @click="doJumpToPage">下一页</a></li>
<li><a class="last" @click="doJumpToPage">尾页</a></li>
<li><a class="rowCount">总记录数({{rowCount}})</a></li>
<li><a class="pageCount">总页数({{pageCount}})</a></li>
<li><a class="pageCurrent">当前页({{pageCurrent}})</a></li>
</ul>
</div>
</div>
<script src="/js/axios.min.js"></script>
<script src="/js/vue.js"></script>
<script>
var vm=new Vue({
el:"#app",
data:{
vouchers:{},
pageCurrent:1,
pageCount:0,
rowCount:0
},
methods:{
doFindPageObjects(){
let url=`voucher/doFindPageObjects/${this.pageCurrent}/3`;
axios.get(url).then(function(result){
let respData=result.data.data;
this.vm.vouchers=respData.records;
this.vm.rowCount=respData.rowCount;
this.vm.pageCount=respData.pageCount;
this.vm.pageCurrent=respData.pageCurrent;
})
},
doJumpToPage(event){
let cls=event.target.getAttribute("class");
if(cls=="first"){
this.pageCurrent=1;
}else if(cls=="pre"&& this.pageCurrent>1){
this.pageCurrent--;
}else if(cls=="next"&& this.pageCurrent< this.pageCount){
this.pageCurrent++;
}else if(cls=="last"){
this.pageCurrent= this.pageCount;
}else{
return;
}
//3.基于新的页码值重新执行查询
this.doFindPageObjects();
}
},
mounted:function(){
this.doFindPageObjects();
}
});
</script>
</body>
</html>
</code></pre><h3>启动服务器进行访问测试分析</h3><p>首先,启动tomcat,然后在打开浏览器,在地址栏输入访问地址,获取服务端数据并进行呈现,如图所示:<br><img src="/img/bVcHzZl" alt="image.png" title="image.png"></p><h2>购物券删除业务实现</h2><h3>业务描述</h3><p>从数据库查询购物券信息后,点击页面上删除按钮,基于id删除当前行记录,如图所示:</p><p><img src="/img/bVcHz5V" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>在购物券呈现页面,用户执行删除操作,其删除时序如图所示:</p><p><img src="/img/bVcHz5E" alt="image.png" title="image.png"></p><h3>Dao接口方法及映射定义</h3><p>在VoucherDao接口中定义删除方法以及SQL映射,代码如下:</p><pre><code> @Delete("delete from tb_voucher where id=#{id}")
int deleteById(Integer id);</code></pre><h3>Service接口方法定义及实现</h3><p>在VoucherService接口中添加删除方法,代码如下:</p><pre><code>int deleteById(Integer id);</code></pre><p>在VoucherService的实现类VoucherServiceImpl中添加deleteById方法实现。代码如下。</p><pre><code>@Override
public int deleteById(Integer id) {
int rows=voucherDao.deleteById(id);
return rows;
}</code></pre><h3>Controller对象方法定义及实现</h3><p>在VoucherController中的添加doDeleteById方法,代码如下:</p><pre><code> @DeleteMapping("/voucher/doDeleteById/{id}")
public ResponseResult doDeleteById(@PathVariable Integer id){
voucherService.deleteById(id);
return new ResponseResult(id);
}
</code></pre><p>说明:Restful 风格为一种软件架构编码风格,定义了一种url的格式,其url语法为/a/b/{c}/{d},在这样的语法结构中{}为一个变量表达式。假如我们希望在方法参数中获取rest url中变量表达式的值,可以使用@PathVariable注解对参数进行描述。</p><h3>Voucher页面上删除按钮事件处理</h3><p>在voucher.html页面中添加删除事件,代码如下:</p><pre><code><button @click="doDeleteById(v.id)" class="btn btn-danger btn-xs">delete</button></code></pre><p>在vue的methods对象中添加删除方法,代码如下:</p><pre><code>doDeleteById(id){
let url=`voucher/doDeleteById/${id}`;
axios.delete(url).then(function(result){
alert(result.data.message);
this.vm.doFindPageObjects();
})
}</code></pre><h3>启动tomcat服务器进行访问测试分析</h3><p>启动tomcat,然后在打开浏览器,在地址栏输入访问地址,获取服务端数据并进行呈现,接下来点击页面上的删除按钮进行测试。</p><h2>购物券添加业务设计实现</h2><h3>业务描述</h3><p>在购物券页面中设计添加表单,然后点击表单中的Save 按钮时将表单数据异步提交到服务端进行保存,如图所示:</p><p><img src="/img/bVcHBag" alt="image.png" title="image.png"></p><h3>业务时序设计及分析</h3><p>在购物券页面,输入购物券信息,然后提交到服务端进行保存,其时序分析如图所示:</p><h3>DAO接口方法设计及实现</h3><p>在VoucherDao中添加用于保存商品信息的接口方法,代码如下:</p><pre><code>int insertObject(Voucher entity);</code></pre><p>在VoucherMapper文件中添加insertObject方法对应的SQL映射,代码如下:</p><pre><code><?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.cy.retail.voucher.dao.VoucherDao">
<insert id="insertObject">
insert into tb_voucher
(deno,requirement,startTime,endTime,maxNum,remark,createdTime)
values
(#{deno},#{requirement},#{startTime},#{endTime},#{maxNum},#{remark},now())
</insert>
</mapper></code></pre><h3>Service接口方法设计及实现</h3><p>在VoucherService接口中添加业务方法,用于实现购物券信息添加,代码如下:</p><pre><code>int saveObject(Voucher entity);</code></pre><p>在VoucherSerivceImpl类中添加接口方法实现,代码如下:</p><pre><code>@Override
public int saveObject(Voucher entity) {
return voucherDao.insertObject(entity);
}
</code></pre><h3>Controller类中方法设计及实现</h3><p>在VoucherController类中添加用于处理添加请求的方法,代码如下:</p><pre><code>@PostMapping("/voucher/doSaveObject")
public ResponseResult doSaveObject(@RequestBody Voucher entity){
System.out.println(entity);
voucherService.saveObject(entity);
return new ResponseResult("save ok");
}</code></pre><h3>购物券页面表单元素设计及事件处理</h3><p>在voucher.html页面代码中设计添加表单,代码如下:</p><pre><code><div>
<form>
<input type="hidden" v-model="id">
<div class="form-group">
<label for="denoId">面值</label>
<input type="text" v-model="deno" class="form-control" id="denoId" placeholder="面值">
</div>
<div class="form-group">
<label for="requirementId">应用条件</label>
<input type="text" v-model="requirement" class="form-control" id="requirementId" placeholder="应用条件">
</div>
<div class="form-group">
<label for="startTimeId">起始日期</label>
<input type="text" v-model="startTime" class="form-control" id="startTimeId" placeholder="起始日期">
</div>
<div class="form-group">
<label for="endTimeId">截至日期</label>
<input type="text" v-model="endTime" class="form-control" id="endTimeId" placeholder="截至日期">
</div>
<div class="form-group">
<label for="maxNumId">最大数量</label>
<input type="text" v-model="maxNum" class="form-control" id="maxNumId" placeholder="最大数量">
</div>
<div class="form-group">
<label for="maxNumId">备注</label>
<textarea v-model="remark" class="form-control" id="remarkId" placeholder="备注"></textarea>
</div>
<div class="form-group">
<input type="button" @click="doSaveOrUpdate" value="Save Data" class="btn btn-primary"/>
</div>
</form>
</div></code></pre><p>在Vue对象的Data区添加属性数据,代码如下:</p><pre><code>deno:"",
requirement:"",
startTime:"",
endTime:"",
maxNum:"",
remark:"",</code></pre><p>在Vue对象中的Methods区,添加保存按钮事件处理函数,代码如下:<br>doSaveOrUpdate(){</p><pre><code> let url="voucher/doSaveObject";
let params={"id":this.id,"deno":this.deno,"requirement":this.requirement,"startTime":this.startTime,"endTime":this.endTime,"maxNum":this.maxNum,"remark":this.remark};
console.log(params);
axios.post(url,params).then(function(result){
alert(result.data.message);
this.vm.doFindPageObjects();
this.vm.id="";
this.vm.deno="";
this.vm.requirement="";
this.vm.startTime="";
this.vm.endTime="";
this.vm.maxNum="";
this.vm.remark="";
})
}</code></pre><h2>购物券修改业务设计实现</h2><h3>业务描述</h3><p>在购物券页面中设计修改按钮,并在点击修改按钮时候基于id查询购物券信息,然后呈现在表单中,如图所示:</p><p><img src="/img/bVcHBp1" alt="image.png" title="image.png"></p><h3>业务时序设计及分析</h3><p>基于用户更新设计其执行时序。</p><h3>Dao接口方法设计及实现</h3><p>在VoucherDao中添加基于id查询和更新的方法,代码如下:</p><pre><code>@Select("select * from tb_voucher where id=#{id}")
Voucher findById(Integer id);</code></pre><pre><code>int updateObject(Voucher entity);</code></pre><p>将updateObject方法的SQL映射添加到映射文件(建议SQL内容比较多的写到映射文件),代码如下:</p><pre><code><update id="updateObject">
update tb_voucher
set
deno=#{deno},
requirement=#{requirement},
startTime=#{startTime},
endTime=#{endTime},
maxNum=#{maxNum},
remark=#{remark}
where id=#{id}
</update></code></pre><h3>Service接口方法设计及实现</h3><p>在VoucherService中添加基于id查询和更新的方法,代码如下:</p><pre><code>Voucher findById(Integer id);</code></pre><pre><code>int updateObject(Voucher entity);</code></pre><p>在VoucherServiceImpl类中添加基于id查询和更新的方法,代码如下:</p><pre><code>@Override
public Voucher findById(Integer id) {
return voucherDao.findById(id);
}</code></pre><pre><code>@Override
public int updateObject(Voucher entity) {
return voucherDao.updateObject(entity);
}</code></pre><h3>Controller类中方法设计及实现</h3><p>在VoucherController类中添加处理基于id的查询请求方法和更新购物券信息的方法,代码如下:</p><pre><code>@GetMapping("/voucher/doFindById/{id}")
public ResponseResult doFindById(@PathVariable Integer id){
Voucher voucher=voucherService.findById(id);
return new ResponseResult(voucher);
}</code></pre><pre><code>@PostMapping("/voucher/doUpdateObject")
public ResponseResult doUpdateObject(@RequestBody Voucher entity){
voucherService.updateObject(entity);
return new ResponseResult("update ok");
}</code></pre><h3>Voucher页面中更新设计设计及实现</h3><p>在voucher页面设计中,在vue对象中的methods区添加基于id的查询请求方法,代码如下:</p><pre><code>doFindById(id){
let url=`voucher/doFindById/${id}`;
axios.get(url).then(function(result){
console.log(result.data);
this.vm.id=result.data.data.id;
this.vm.deno=result.data.data.deno;
this.vm.requirement=result.data.data.requirement;
this.vm.startTime=result.data.data.startTime;
this.vm.endTime=result.data.data.endTime;
this.vm.maxNum=result.data.data.maxNum;
this.vm.remark=result.data.data.remark;
})
}</code></pre><p>在voucher页面设计中,在vue对象中的methods区修改doSaveOrUpdate方法,代码如下:</p><pre><code>doSaveOrUpdate(){
let url=this.id?"voucher/doUpdateObject":"voucher/doSaveObject";
let params={"id":this.id,"deno":this.deno,"requirement":this.requirement,"startTime":this.startTime,"endTime":this.endTime,"maxNum":this.maxNum,"remark":this.remark};
console.log(params);
axios.post(url,params).then(function(result){
alert(result.data.message);
this.vm.doFindPageObjects();
this.vm.id="";
this.vm.deno="";
this.vm.requirement="";
this.vm.startTime="";
this.vm.endTime="";
this.vm.maxNum="";
this.vm.remark="";
})
}
</code></pre><h3>启动服务器进行访问测试分析</h3><h2>总结(Summary)</h2><p>本小节重点讲解了SpringBoot工程下MyBatis,SpringMVC,Vue技术的综合应用,重点理解其业务实现过程以及问题的解决过程。</p>
15-SpringBoot工程中前端JS问题调试分析
https://segmentfault.com/a/1190000037502743
2020-10-16T18:00:15+08:00
2020-10-16T18:00:15+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
27
<h2>项目中客户端JS常见问题</h2><p><img src="/img/bVcHuRK" alt="image.png" title="image.png"></p><p><img src="/img/bVcHuRM" alt="image.png" title="image.png"></p><p><img src="/img/bVcHuRP" alt="image.png" title="image.png"></p><p><img src="/img/bVcHvRL" alt="image.png" title="image.png"></p><p><img src="/img/bVcHvR0" alt="image.png" title="image.png"></p><p><img src="/img/bVcHvSe" alt="image.png" title="image.png"></p><p><img src="/img/bVcHvSJ" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节主要是列出了几个客户端常见问题,这些问题的解决要结合业务进行断点,日志,排除法进行分析,然后从实践中进行提高.</p>
14-SpringBoot+Mybatis+Vue 实现商品模块的crud操作
https://segmentfault.com/a/1190000037468625
2020-10-14T12:09:42+08:00
2020-10-14T12:09:42+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
55
<h2>准备工作</h2><p>第一步 创建新module,名字为10-springboot-goods-vue.<br>第二步 添加maven依赖并进行初步配置(拷贝即可)<br>第三步 拷贝pojo,dao,service包中的所有接口和类.<br>第四步 拷贝静态资源到static目录(例如vue.js,axios.min.js)</p><h2>商品查询设计及实现</h2><p>创建GoodsController并定义相关方法,代码如下:</p><pre><code>package com.cy.pj.goods.controller;
import com.cy.pj.goods.pojo.Goods;
import com.cy.pj.goods.service.GoodsService;
import java.util.List;
@RestController
public class GoodsController {
@Autowired
private GoodsService goodsService;
/**查询所有商品信息*/
@GetMapping("/goods/doFindGoods")
public List<Goods> doFindGoods(){
return goodsService.findGoods();
}
}</code></pre><p>在项目static目录创建goods-vue.html,并基于vue呈现数据,代码如下</p><pre><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h1>The Goods Page</h1>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>remark</th>
<th>createdTime</th>
</tr>
</thead>
<tbody>
<tr v-for="g in goods">
<td>{{g.id}}</td>
<td>{{g.name}}</td>
<td>{{g.remark}}</td>
<td>{{g.createdTime}}</td>
</tr>
</tbody>
</table>
</div>
<script src="js/axios.min.js"></script>
<script src="js/vue.js"></script>
<script>
var vm=new Vue({//vue对象时vue.js应用的入口对象
el:"#app",//vue对象要监控的区域
data:{//此对象用于同步页面数据的一个对象
goods:{}
},
methods:{//同步与页面元素事件处理函数
doFindGoods:function(){
let url="goods/doFindGoods";
axios.get(url)
.then(function(result){
this.vm.goods=result.data;
});
}
},
mounted:function(){
this.doFindGoods();
}
});
</script>
</body>
</html></code></pre><p>启动tomcat进行访问测试,如图所示:<br><img src="/img/bVcHnsH" alt="image.png" title="image.png"></p><h2>商品删除设计及实现</h2><p>在控制层方法中添加,处理删除逻辑的方法,代码如如下:</p><pre><code>@RequestMapping("/goods/doDeleteById/{id}")
public String doDeleteById(@PathVariable("id") Integer id){
System.out.println("delete id "+id);
goodsService.deleteById(id);
return "delete ok";
}</code></pre><p>在商品列表中的tr对象内部添加删除元素,代码如下:</p><pre><code><td><a @click="doDeleteById(g.id)">删除</a></td></code></pre><p>在商品模块的vue对象中添加执行删除逻辑的方法,代码如下:</p><pre><code>doDeleteById:function(id){
var url="goods/doDeleteById/"+id;
axios.get(url)
.then(function(res){
alert(res.data);
this.vm.doFindGoods();
})
}</code></pre><p>启动服务进行访问测试,检测其结果。</p><h2>商品添加设计及实现</h2><p>在Controller类中添加用于处理商品添加逻辑的方法,代码如下:</p><pre><code>@RequestMapping("/goods/doSaveGoods")
public String doSaveGoods(@RequestBody Goods entity){
System.out.println("entity="+entity);
goodsService.saveGoods(entity);
return "save ok";
}</code></pre><p>在Goods页面上添加表单元素,用于实现用户输入,代码如下:</p><pre><code><form>
<ul>
<li>name</li>
<li><input v-model="name"></li>
<li>remark</li>
<li><textarea v-model="remark"></textarea></li>
<li><input type="button" @click="doSaveOrUpdateGoods" value="Save Goods"></li>
</ul>
</form></code></pre><p>在vue对象内部添加用于同步表单数据的Data属性内容,代码如下:</p><pre><code>data:{
name:"",
remark:"",
goods:"",
}</code></pre><p>在vue对象内部添加处理添加操作的事件处理函数,代码如下:</p><pre><code>doSaveOrUpdateGoods:function(){
var params={"name":this.name,"remark":this.remark};
var url="goods/doSaveGoods";
axios.post(url,params)
.then(function(res){
alert(res.data);
this.vm.doFindGoods();
this.vm.name="";
this.vm.remark="";
});
}</code></pre><p>启动服务,进行添加操作测试。</p><h2>商品修改设计及实现</h2><p>在Controller中添加基于商品id查询和更新商品信息的方法,代码如下:</p><pre><code>@RequestMapping("/goods/doFindById/{id}")
public Goods doFindById(@PathVariable("id") Integer id){
return goodsService.findById(id);
}</code></pre><pre><code>@RequestMapping("goods/doUpdateGoods")
public String doUpdateGoods(@RequestBody Goods entity){
goodsService.updateGoods(entity);
return "update ok";
}
</code></pre><p>在Goods页面表单中添加隐藏的表单元素用于记录id值,代码如下:</p><pre><code><li><input type="hidden" v-model="id"></li></code></pre><p>在Goods页面记录中添加修改操作的需要的a元素,代码如下:</p><pre><code><td><a @click="doFindById(g.id)">修改</a></td></code></pre><p>在Vue对象中添加基于id查询的方法,代码如下:</p><pre><code>doFindById:function(id){
var url="goods/doFindById/"+id;
axios.get(url)
.then(function(res){
console.log(res.data);
this.vm.id=res.data.id;
this.vm.name=res.data.name;
this.vm.remark=res.data.remark;
})
}</code></pre><p>修改Vue对象中的用于保存和修改数据的方法,代码如下:</p><pre><code>doSaveOrUpdateGoods:function(){
var params={"id":this.id,"name":this.name,"remark":this.remark};
var url=this.id?"goods/doUpdateGoods":"goods/doSaveGoods";
axios.post(url,params)
.then(function(res){
this.vm.doFindGoods();
alert(res.data);
this.vm.id="";
this.vm.name="";
this.vm.remark="";
});
}</code></pre><p>启动服务进行访问测试,检测其结果。</p><h2>总结(Summary)</h2><p>本小节主要基于vue和axio技术实现了商品模块的基本操作,重点掌握客户端与服务端的交互和传值过程。</p>
12-SpringBoot 工程中的响应标准设计及实现
https://segmentfault.com/a/1190000037435863
2020-10-12T08:34:46+08:00
2020-10-12T08:34:46+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
85
<h2>背景分析</h2><p>在基于C/S架构的编程模型中,客户端往往需要对服务端返回的数据,基于状态的不同进行不同的处理。例如,正确的状态数据一种呈现方式,错误的状态数据是另外一种呈现方式。于是服务端响应数据的标准化设计油然而生。</p><h2>响应标准设计</h2><p>在响应数据标准化设计时,首先要对响应数据进行分析,哪些数据要响应到客户端,对这些数据进行怎样的状态设计等。假如现在响应的业务数据包含三部分:状态,消息,具体数据。我们可以这样设计,例如:</p><pre><code>package com.cy.pj.common.pojo;
/**
* 基于此对象封装服务端响应到客户端的数据
*/
public class ResponseResult {
/**响应状态码(有的人用code)*/
private Integer state=1;//1表示ok,0表示error,.....
/**状态码对应的信息*/
private String message="ok";
/**正确的响应数据*/
private Object data;
public ResponseResult(){}
public ResponseResult(String message){//new ResponseResult("delete ok"),
this.message=message;
}
public ResponseResult(Object data){//new ResponseResult(list);
this.data=data;
}
public ResponseResult(Throwable e){//new ResponseResult(e);
this.state=0;
this.message=e.getMessage();
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}</code></pre><h2>响应数据的封装</h2><p>在Controller类的逻辑方法中进行正常的响应数据封装,例如:</p><pre><code>package com.cy.pj.module.controller;
import com.cy.pj.common.pojo.ResponseResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ArithmeticController {
@RequestMapping("/doCompute/{n1}/{n2}")
public ResponseResult doCompute(@PathVariable Integer n1, @PathVariable Integer n2){
Integer result=n1/n2;
ResponseResult r=new ResponseResult("计算结果:"+result);
r.setData(result);
return r;
}
}</code></pre><p>在全局异常处理对象中进行异常响应数据的封装,例如:</p><pre><code>package com.cy.pj.common.web;
import com.cy.pj.common.pojo.ResponseResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);//2
@ExceptionHandler(ArithmeticException.class)
public ResponseResult doHandleArithmeticException(ArithmeticException e){
e.printStackTrace();
log.info("exception {}",e.getMessage());
return new ResponseResult(e);//封装异常结果
}
}</code></pre><h2>总结(Summary)</h2><p>本小节主要讲解了Spring Boot工程中对逻辑数据的响应如何进行标准化设计,为什么这样设计,这样设计的好处以及如何设计。通过对这一小节的学习要提高其响应规范维度的设计理念,掌握基本技能。</p>
11-SpringBoot 工程中的异常处理方式
https://segmentfault.com/a/1190000037435700
2020-10-12T08:01:08+08:00
2020-10-12T08:01:08+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
71
<h2>背景分析</h2><p>在项目的开发中,不管是对底层的数据逻辑操作过程,还是业务逻辑的处理过程,还是控制逻辑的处理过程,都不可避免会遇到各种可预知的、不可预知的异常。处理好异常对系统有很好的保护作用,同时会大大提高用户的体验。</p><h2>异常处理分析</h2><h3>概述</h3><p>Java项目中处理异常方式无非两种,要么执行trycatch操作,要么执行throw操作(抛给其它对象处理),无论采用哪种方式,其目的是让我们的系统对异常要有反馈。但现在的问题是我们如何让这种反馈代码的编写即简单又直观、友好。</p><h3>处理规范</h3><p>我们在处理异常的过程中通常要遵循一定的设计规范,例如:</p><ul><li>捕获异常时与抛出的异常必须完全匹配,或者捕获异常是抛出异常的父类类型。</li><li>避免直接抛出RuntimeException,更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常(例如ServiceException)。</li><li>捕获异常后必须进行处理(例如记录日志)。如果不想处理它,需要将异常抛给它的调用者。</li><li>最外层的逻辑必须处理异常,将其转化成用户可以理解的内容。</li><li>避免出现重复的代码(Don’t Repeat Yourself),即DAY原则。</li></ul><h2>SpringBoot 工程下的异常处理</h2><h2>准备工作</h2><p>第一步:创建项目或module,并添加web依赖,代码如下:</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency></code></pre><p>第二步:修改项目访问端口为80,例如</p><pre><code>server.port=80</code></pre><p>第三步:定义Controller类,代码如下:</p><pre><code>package com.cy.pj.arithmetic.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ArithmeticController {
@RequestMapping("doCompute/{n1}/{n2}")
@ResponseBody
public String doCompute(@PathVariable Integer n1,
@PathVariable Integer n2){
Integer result=n1/n2;
return "Result is "+result;
}
}</code></pre><p>第四步启动项目进行访问测试</p><p>在浏览器地址栏输入<a href="https://link.segmentfault.com/?enc=I4reaWz89idMxVXqlUyBJw%3D%3D.Szdh2YN6o3sZmEhkTV7Xt8nTmON2%2FoeZg7FgUgYRnyc%3D" rel="nofollow">http://localhost/doCompute/10/2</a>,检测输出结果。</p><pre><code>Result is 5</code></pre><h3>默认异常处理</h3><p>在浏览器地址栏输入<a href="https://link.segmentfault.com/?enc=8BcMqJjLy0SLPB0kmXDn1w%3D%3D.VziQicF2N4bjhq474yLlGXo1Z422ao9822gIrfkCWrw%3D" rel="nofollow">http://localhost/doCompute/10/0</a>,检测输出结果。</p><p><img src="/img/bVcHdrF" alt="image.png" title="image.png"></p><p>对于这样的默认异常处理(spring boot提供),用户体验不太友好,为了呈现更加友好的异常信息,我们通常要对异常进行自定义处理。</p><h3>自己try异常处理</h3><p>在控制层方法中,我们可以进行try catch处理,例如:</p><pre><code> @RequestMapping("doCompute/{n1}/{n2}")
@ResponseBody
public String doCompute(@PathVariable Integer n1,
@PathVariable Integer n2){
try{
Integer result=n1/n2;
return "Result is "+result;
}catch(ArithmeticException e){
return "exception is "+e.getMessage();
}
}
</code></pre><p>一个Controller类中通常会有多个方法,这样多个方法中都写try语句进行异常处理会带来大量重复代码的编写,不易维护。</p><h3>Controller内部定义异常处理方法</h3><p>在Controller类中添加异常处理方法,代码如下:</p><pre><code>@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public String doHandleArithmeticException(ArithmeticException e){
e.printStackTrace();
return "计算过程中出现了异常,异常信息为"+e.getMessage();
}</code></pre><p>@ExceptionHandler注解描述的方法为异常处理方法(注解中的异常类型为可处理的异常类型),假如Controller类中的逻辑方法中出现异常后没有处理异常,则会查找Controller类中有没有定义异常处理方法,假如定义了,且可以处理抛出的异常类型,则由异常处理方法处理异常。</p><h3>控制层中的全局异常处理类及方法定义</h3><p>当项目由多个控制层类中有多个共性异常的处理方法定义时,我们可以将这些方法提取到公共的父类对象中,但是这种方式是一种强耦合的实现,不利于代码的维护。我们还可以借助spring框架中web模块定义的全局异常处理规范进行实现,例如定义全局异常处理类,代码如下:</p><pre><code>package com.cy.pj.common.web;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String doHandleArithmeticException(ArithmeticException e){
e.printStackTrace();
return "计算过程中出现了异常,异常信息为"+e.getMessage();
}
}
</code></pre><p>其中,@RestControllerAdvice 注解描述的类为全局异常处理类,当控制层方法中的异常没有自己捕获,也没有定义其内部的异常处理方法,底层默认会查找全局异常处理类,调用对应的异常处理方法进行异常处理。如图所示:</p><p><img src="/img/bVcIHCI" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节主要是对springboot中的异常处理机制进行了简单分析和讲解。目的是掌握springboot工程下的异常处理方式,并基于业务的不同进行响应的异常处理。从而有效提高其用户体验,加强系统的容错能力。</p>
10-SpringBoot 工程的健康监控实现
https://segmentfault.com/a/1190000037426380
2020-10-10T17:29:24+08:00
2020-10-10T17:29:24+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
60
<h2>健康监控简述</h2><p>Spring Boot 中actuator模块提供了健康检查,审计、指标收集,HTTP跟踪等功能,可以帮助我们更好的管理和跟踪springboot项目。</p><h2>健康监控配置实现</h2><p>在需要使用健康监控的项目或module中,添加如下依赖:</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency></code></pre><p>添加完依赖以后,reload项目或module。</p><h2>健康监控启动分析</h2><p>启动项目,在浏览器中输入如下地址:(SpringBoot默认打开的监控选项有限)</p><pre><code>http://localhost/actuator</code></pre><p>其访问结果,如图所示:</p><p><img src="/img/bVcHcsj" alt="image.png" title="image.png"></p><p>还可以在actuator列出的选中中进行点击,例如访问health</p><pre><code>http://localhost/actuator/health</code></pre><p>其呈现结果,如图所示(其中up状态表示正常):<br><img src="/img/bVcHcsy" alt="image.png" title="image.png"></p><p>假如希望查看更多actuator选项,可以在spring boot中配置文件</p><p>application.properties中添加如下语句:</p><pre><code>management.endpoints.web.exposure.include=*</code></pre><p>然后,重启服务器,基于访问<a href="https://link.segmentfault.com/?enc=%2FkVGX0bNgk3srz7tB%2FY3Tg%3D%3D.X1r12gHnFJmm5S84Bu9HZVCQRAPBeQ7YQgwFbms511M5pjf9GD%2BdEfRiaW0w5DW15OQ5DJVtSPH3e%2Bw5W8n4MbBmCarzOm4nX6ezkFTCxK0UasjaJunoXl4OSZU5JffGo6UpXJgsbf7jNIFV6RY4N0UwPHocO%2BDdWP7eAHSRlBvLmjcfhMg%2B4Edyb%2FVbWWOdSat9wwkep6ZK7bmVdSiKe4fAa8Jwds3QnxGgvRsUx%2BCKwDdnoyKdeqItTQJBWMQ74jZoskk9typAr3yRUCuHNTuRpst2lV1dLfn97ss7SWVsvvjVI3Bct3%2B608e3OCPyjm4gRdUw4zRqucRj%2F%2B9oOV%2FORre7k4gUhHhqzOwvKWwdjrM%2B0Nf%2F7dPdZwcmWV%2B4W%2F32%2FMZd3Adn4EOF6YMWL7V4zrduuZkHUDd91zN%2FKmE%3D" rel="nofollow">http://localhost/actuator地址...</a></p><h2>总结(Summary)</h2><p>本小节主要是对springboot工程中的健康监控功能做了一个简易分析和实现,自己可以基于业务的需要,进行监控的配置和分析.</p>
09-springboot工程中的热部署实现。
https://segmentfault.com/a/1190000037422592
2020-10-10T14:55:26+08:00
2020-10-10T14:55:26+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
65
<h2>热部署简介</h2><p>Spring Boot 开发者为Spring Boot项目中提供了一个热部署(spring-boot-devtools)模块,支持项目的热部署(修改了某些资源以后无需重启服务),以提高开发效率.其底层其实是借助了两个类加载器做了具体实现,一个类加载器加载不变class,一个类加载器加载可能变化类,以提供类的热部署性能.</p><h2>热部署环境初始化</h2><p>本次热部署的演示在IDEA开发中进行实现,其它工具可以自己通过搜索引擎进行学习。</p><p>IDEA 启动自动编译,如图所示:</p><p><img src="/img/bVcHbqu" alt="image.png" title="image.png"></p><p>IDEA工具中启动注册窗口(按ctrl+shift+alt+/),如图所示:<br><img src="/img/bVcHaYm" alt="image.png" title="image.png"></p><p>选择编译构建配置,如图所示:<br><img src="/img/bVcHaYN" alt="image.png" title="image.png"></p><h2>热部署在项目中应用</h2><p>在需要热部署的项目或module中添加如下依赖:</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency></code></pre><p>依赖下载以后,可重启reload项目,然后当我们修改了src/main/java目录下的java文件或修改了src/main/resources目录下的配置文件时,默认都会重启你的web服务器,但是修改了测试类或html文件不会自动重启和部署。</p><h2>总结(summary)</h2><p>本小节对springboot工程中的热部署进行了分析和实现,可以idea工具中实践整个过程。</p>
08-springboot 工程下lombok的应用。
https://segmentfault.com/a/1190000037418779
2020-10-10T12:36:52+08:00
2020-10-10T12:36:52+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
62
<h2>背景分析</h2><p>在实际的java项目中我们创建的所有pojo类几乎都要为属性添加set/get/toString等相关方法,所有的日志记录相关类可能都要创建日志等对象,这些样板代码既没有技术含量,又影响着代码的美观,同时重复的编码过程会在无形中加大我们的工作量。 此时Lombok应运而生。</p><h2>lombok简介</h2><h3>概述</h3><p>Lombok是一个第三的Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来告诉编译过程中的编译工具,在源代码编译成字节码的过程中,在字节码中添加一些量样板代码。</p><h3>常用注解分析</h3><ul><li>@Setter 用于为描述的类生成setter方法,不包含final修饰属性。</li><li>@Getter 用于为描述的类生成getter方法。</li><li>@ToString 用于为描述的类添加toString方法。</li><li>@EqualsAndHashCode 用于为描述的类,生成hashCode和equals方法。</li><li>@NoArgsConstructor 用于为描述的类生成无参的构造方法。</li><li>@AllArgsConstructor 用于为描述的类生成包含类中所有字段的构造方法。</li><li>@Data用于为描述的类生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。</li><li>@Slf4J 用于为描述的类添加一个日志属性对象。</li></ul><h2>lombok安装</h2><h3>idea中的安装配置</h3><p>第一步:打开idea的设置窗口,找到plugins菜单,搜索lombok进行安装,如图所示:</p><p><img src="/img/bVcHaim" alt="image.png" title="image.png"></p><p>第二步:启动注解处理,如图所示:</p><p><img src="/img/bVcHakZ" alt="image.png" title="image.png"></p><p>第三步:重启idea(可选,有的idea版本需要)。</p><h3>sts中的安装配置</h3><p>自己百度尝试。</p><h2>lombok在maven项目中应用</h2><p>第一步:添加lombok依赖。</p><pre><code><dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>annotationProcessor</scope>
</dependency></code></pre><p>第二步:在类上应用lombok注解。</p><pre><code>@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {
private Long id;
private String name;
private String remark;
private Date createdTime;
}</code></pre><p>第三步:编写单元测试类检测Lombok注解应用</p><pre><code>@Slf4j
@SpringBootTest
public class GoodsTests{
@Test
void testGoods(){
Goods g=new Goods();
g.setId(100L);
g.setName("Lombok");
log.info("id的值为{}",g.getId());
log.info("name的值为{}",g.getName());
}
}</code></pre><h2>总结(Summary)</h2><p>本小节主要对lombok做了一个分析,安装和配置,并结合实际项目讲解了lombok的应用场景及具体应用过程。</p>
06-SpringBoot工程下Spring MVC技术的应用?
https://segmentfault.com/a/1190000025178018
2020-09-30T12:34:16+08:00
2020-09-30T12:34:16+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
151
<h2>Spring MVC 简介</h2><h3>背景分析</h3><p>在大型软件系统设计时,业务一般会相对复杂,假如所有业务实现的代码都纠缠在一起,会出现逻辑不清晰、可读性差,维护困难,改动一处就牵一发而动全身等问题。为了更好解决这个问题就有了我们现在常说的分层架构设计。</p><h3>MVC 是什么</h3><p>MVC是一种软件架构设计思想,基于MVC架构将我们的应用软件进行分层设计和实现,例如可以分为视图层(View),控制层(Controller),模型层(Model),通过这样的分层设计让我们程序具备更好的灵活性和可可扩展性.因为这样可以将一个复杂应用程序进行简化,实现各司其职,各尽所能.比较适合一个大型应用的开发.</p><h3>Spring MVC 概述</h3><p>Spring MVC是MVC设计思想在Spring框架中的一种实现,基于这样的思想spring框架设计了一些相关对象,用于更好的基于MVC架构处理请求和响应,其简易架构如图所示:</p><p><img src="/img/bVbRNw3" alt="image.png" title="image.png"></p><p>其中:<br>1)DispatcherServlet是客户端所有请求处理的入口,负责请求转发。<br>2)RequestMapping负责存储请求url到后端handler对象之间的映射。<br>3)Handler 用于处理DispatcherServlet对象转发过来的请求数据。<br>4)ViewResolver负责处理所有Handler对象响应结果中的view。</p><h2>Spring MVC 快速入门</h2><h3>准备工作</h3><p>第一步:创建项目module,基本信息如图所示:</p><p><img src="/img/bVbRN4r" alt="image.png" title="image.png"></p><p>第二步:添加项目依赖(可以在module创建时,也可以创建后),代码如下:</p><p>Spring Web 依赖(提供了spring mvc支持并且会嵌入一个tomcat)</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency></code></pre><p>Thymeleaf 依赖(提供了以html作为页面模板进行解析和操作的相关对象)</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency></code></pre><p>第三步:启动项目检测控制台启动状态是否OK</p><h3>static 目录分析及应用</h3><p>static 目录为springboot工程创建时添加了web依赖以后自动创建的目录,此目录中可以存储html、css、js、image,这些资源可以在启动服务器以后,直接在浏览器进行访问。例如:<br>第一步:在static目录下创建一个index.html页面,代码如下:</p><pre><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>The First Html Page</h1>
</body>
</html></code></pre><p>第二步:启动服务器并直接进行访问测试,如图所示</p><p><img src="/img/bVbRN5m" alt="image.png" title="image.png"></p><h3>templates 目录分析及应用</h3><p>templates 目录为springboot工程创建时添加了thymeleaf依赖以后自动创建的目录,此目录中要存储一些html模板,这个模板页面不能直接通过浏览器url进行访问,需要基于后端控制器,在方法中定义页面响应,例如:</p><p>第一步:定义TemplateController及方法,代码如下:</p><pre><code>package com.cy.pj.health.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TemplateController {
@RequestMapping("doTemplateUI")
public String doTemplateUI(){
return "default";
}
}</code></pre><p>第二步:在templates目录中定义模板页面default.html,代码如下:</p><pre><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>The Default Template page</h1>
</body>
</html></code></pre><p>其中,假如default.html要在放在templates子目录中,则还需要在配置文件中配置thymeleaf的前缀,例如:</p><pre><code>spring.thymeleaf.prefix=classpath:/templates/module/</code></pre><p>第三步:启动服务进行访问测试,如图所示:</p><p><img src="/img/bVbRO3O" alt="image.png" title="image.png"></p><p>第四步:访问过程中的BUG分析</p><p><img src="/img/bVcIsP1" alt="image.png" title="image.png"></p><p><img src="/img/bVcIsQS" alt="image.png" title="image.png"></p><p><img src="/img/bVcIsQ6" alt="image.png" title="image.png"></p><p>第五步:thymeleaf页面缓存设置<br>假如我们希望在修改了页面内容以后,不重新启动服务,直接刷新即可看页面改动,可以执行如下操作:<br>修改配置文件内容,添加如下语句:</p><pre><code>spring.thymeleaf.cache=false</code></pre><p>修改idea默认配置,允许自动编译,如图所示:<br><img src="/img/bVcIsRz" alt="image.png" title="image.png"></p><p><img src="/img/bVcIsRX" alt="image.png" title="image.png"></p><h2>SpringMVC 响应数据处理</h2><h3>ModelAndView 应用</h3><p>我们有一业务,现在需要将响应数据封装到ModelAndView对象,然后响应到客户端,如何实现呢?</p><p>第一步:定义ModelViewController以及方法,代码如下:</p><pre><code>package com.cy.pj.module.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ModelViewController {
@RequestMapping("/doModelAndView")
public String doModelAndView(Model model) {
model.addAttribute("username", "jason");
model.addAttribute("state", true);
return "view";
}
}</code></pre><p>第二步:在templates相关目录下定义view.html,并在页面中添加呈现数据的代码,例如:</p><pre><code>
<div>
<ul>
<li>username:[[${username}]]</li>
<li>state:[[${state}]]</li>
</ul>
</div></code></pre><p>第三步:启动项目进行访问测试,并检测输出结果,例如:</p><p><img src="/img/bVbRO6y" alt="image.png" title="image.png"></p><p>第四步:运行过程中的结果分析</p><p><img src="/img/bVcIsRb" alt="image.png" title="image.png"></p><h3>JSON数据响应</h3><p>我们有一业务不需要页面,只需要将响应数据转换为json,然后响应到客户端,如何实现呢?<br>第一步:定义ResponseResult对象用于封装响应数据,例如:</p><pre><code>package com.cy.pj.module.pojo;
public class ResponseResult {
private Integer code;
private String message;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}</code></pre><p>第二步:定义JsonObjectController以及方法,代码如下:</p><pre><code>package com.cy.pj.module.controller;
@RestController
public class JsonObjectController {
@RequestMapping("/doConvertResponseToJson")
public ResponseResult doConvertResponseToJson(){
ResponseResult rs=new ResponseResult();
rs.setCode(200);
rs.setMessage("OK");
return rs;
}
@RequestMapping("/doConvertMapToJson")
public Map<String,Object> doConvertMapToJson(){
Map<String,Object> map=new HashMap<>();
map.put("username","刘德华");
map.put("state",true);
return map;
}
@RequestMapping("/doPrintJSON")
public void doPrint(HttpServletResponse response)throws Exception{
Map<String,Object> map=new HashMap<>();
map.put("username","刘德华");
map.put("state",true);
//将map中的数据转换为json格式字符串
ObjectMapper om=new ObjectMapper();
String jsonStr=om.writeValueAsString(map);
System.out.println("jsonStr="+jsonStr);
//将字符串响应到客户端
//设置响应数据的编码
response.setCharacterEncoding("utf-8");
//告诉客户端,要向它响应的数据类型为text/html,编码为utf-8.请以这种编码进行数据呈现
response.setContentType("text/html;charset=utf-8");
PrintWriter pw=response.getWriter();
pw.println(jsonStr);
}
}
</code></pre><p>第三步:启动服务器分别进行访问测试,代码如下:</p><p><img src="/img/bVbRO8d" alt="image.png" title="image.png"></p><p>第四步:启动或访问过程中的问题分析</p><p><img src="/img/bVcIsQ7" alt="image.png" title="image.png"></p><p><img src="/img/bVcIsRi" alt="image.png" title="image.png"></p><h2>SpingMVC 请求参数数据处理</h2><p>我们在执行业务的过程中通常会将一些请求参数传递到服务端,服务端如何获取参数并注入给我们的方法参数的呢?</p><h3>准备工作</h3><p>定义一个controller对象,用户处理客户端请求,例如:</p><pre><code>package com.cy.pj.module.controller;
import com.cy.pj.module.pojo.RequestParameter;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class ParamObjectController {}</code></pre><h3>直接量方式</h3><p>在ParamObjectController中添加方法,基于直接量方式接受客户端请求参数,例如:</p><pre><code>@GetMapping("/doParam01")
public String doMethodParam(String name){
return "request params "+name;
}</code></pre><p>访问时,可以这样传参,例如:</p><pre><code>http://localhost/doParam01?name=beijing</code></pre><h3>POJO对象方式</h3><p>定义pojo对象,用于接受客户端请求参数,例如:</p><pre><code>package com.cy.pj.module.pojo;
public class RequestParameter {
private String name;
//......
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "RequestParam{" +
"name='" + name + ''' +
'}';
}
}</code></pre><p>定义Controller方法,方法中使用pojo对象接收方法参数,例如:</p><pre><code>@RequestMapping("/doParam02")
public String doMethodParam(RequestParameter param){//pojo对象接收请求参数,pojo对象中需提供与参数名相匹配的set方法
return "request params "+param.toString();
}
</code></pre><p>启动服务进行访问测试,可以这样传参,例如:</p><pre><code>http://localhost/doParam02?name=beijing</code></pre><h3>Map对象方式</h3><p>有时候我们不想使用pojo对象接收请求参数,我们可以使用map对象来接收,又该如何实现呢?</p><p>定义Controller方法,基于map对象接收请求参数,例如:</p><pre><code>@GetMapping("/doParam03")
public String doMethodParam(@RequestParam Map<String,Object> param){
return "request params "+param.toString();
}</code></pre><p>其中,map接收请求参数,必须使用@RequestParam对参数进行描述.</p><p>启动服务进行访问测试,可以这样传参,例如:</p><pre><code>http://localhost/doParam03?name=beijing</code></pre><h2>总结(Summary)</h2><p>本章节对springboot工程下spring mvc技术的应用做了一个入门实现,并结合实际项目中的业务应用,讲解了MVC中请求数据的获取和响应数据处理的一个基本过程.</p>
05-SpringBoot工程中的MyBatis框架的整合实现及原理分析
https://segmentfault.com/a/1190000025162284
2020-09-29T08:48:15+08:00
2020-09-29T08:48:15+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
191
<h2>整合MyBatis的初步分析</h2><h3>概述</h3><p>Mybatis是一个优秀的持久层框架,底层基于JDBC实现与数据库的交互。并在JDBC操作的基础上做了封装和优化,它借助灵活的SQL定制,参数及结果集的映射方式,更好的适应了当前互联网技术的发展。Mybatis框架的简单应用架构如图所示:<br><img src="/img/bVbRGhx" alt="image.png" title="image.png"><br>在当今的互联网应用中项目,mybatis框架通常会由spring框架进行资源整合,作为数据层技术实现数据交互操作。</p><h3>准备工作</h3><p>第一步:创建项目module,例如:</p><p><img src="/img/bVbRKMJ" alt="image.png" title="image.png"></p><p>第二步:添加依赖</p><p>mysql 驱动依赖</p><pre><code><dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency></code></pre><p>spring jdbc 依赖</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency></code></pre><p>mybatis starter依赖</p><pre><code><dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
</code></pre><p>第二步:application.properties 配置文件中添加简易配置</p><p>连接池配置</p><pre><code>spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root</code></pre><p>mybatis配置</p><pre><code>mybatis.mapper-locations=classpath:/mapper/*/*.xml</code></pre><h3>环境测试代码实现</h3><p>在src/test/java目录中添加测试类,对mybatis框架整合进行基本测试,代码如下:</p><pre><code>package com.cy.pj.goods.dao;
import java.sql.Connection;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MyBatisTests {
@Autowired
private SqlSession sqlSession;
@Test
public void testGetConnection() {
Connection conn=sqlSession.getConnection();
System.out.println("connection="+conn);
}
}</code></pre><p>在SpringBoot脚手架工程中,Spring框架会基于MyBatis框架底层配置,创建SqlSessionFactory对象,然后再通过此工厂对象创建SqlSession,最后基于Springku框架为测试类注入SqlSession对象,接下来,我们可以通过SqlSession对象实现与数据库的会话了。</p><p><img src="/img/bVbRGju" alt="image.png" title="image.png"></p><h2>整合MyBatis业务代码实现及原理分析</h2><h3>业务描述</h3><p>基于SpringBoot脚手架工程对MyBatis框架的整合,实现对商品库中商品数据的查询业务。</p><h3>API架构设计</h3><p><img src="/img/bVbRGkM" alt="image.png" title="image.png"></p><h3>业务时序图分析</h3><p><img src="/img/bVbRKSj" alt="image.png" title="image.png"></p><h3>业务代码设计及实现</h3><p>第一步:定义商品模块POJO对象类型(基于此对象存储商品数据),代码如下:</p><pre><code>package com.cy.pj.goods.pojo;
import java.util.Date;
/**用于存储商品信息的pojo对象*/
public class Goods {
private Long id;
private String name;
private String remark;
private Date createdTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
}</code></pre><p>第二步:定义商品模块持久层对象GoodsDao接口及方法映射,代码如下</p><p>GoodsDao接口及方法定义</p><pre><code>package com.cy.pj.goods.dao;
import com.cy.pj.goods.pojo.Goods;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**商品模块持久层对象,基于此对象的实现类操作商品库中的数据
* @Mapper MyBatis框架中定义,用于描述持久层接口,告诉mybatis
* 这个接口的实现类由mybatis创建,并且交给spring框架管理.
* */
@Mapper
public interface GoodsDao {
List<Goods> findGoods();
}</code></pre><p>GoodsDao接口映射文件及SQL映射定义</p><pre><code><?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.cy.pj.goods.dao.GoodsDao">
<select id="findGoods" resultType="com.cy.pj.goods.pojo.Goods">
select * from tb_goods
</select>
</mapper></code></pre><h3>测试代码的编写及运行</h3><p>定义单元测试类,对GoodsDao方法进行单元测试,例如:</p><pre><code>package com.cy.pj.goods.dao;
@SpringBootTest
public class GoodsDaoTests {
@Autowired
private GoodsDao goodsDao;
@Test
void testFindGoods(){
List<Goods> list=goodsDao.findGoods();
for(Goods g:list){
System.out.println(g);
}
}
}</code></pre><p>测试运行,底层API分析,如图所示:</p><p><img src="/img/bVbRMjt" alt="image.png" title="image.png"></p><h3>测试运行过程中的BUG分析</h3><ul><li>idea中@autowired注解错误提示配置,如图:</li></ul><p><img src="/img/bVbRK6n" alt="image.png" title="image.png"></p><ul><li>BindingException,如图所示:</li></ul><p><img src="/img/bVbRK6g" alt="image.png" title="image.png"></p><ul><li>单元测试类位置错误,如图所示:</li></ul><p><img src="/img/bVbRMhT" alt="image.png" title="image.png"></p><ul><li>找不到对应的映射元素,如图所示:</li></ul><p><img src="/img/bVbRMi6" alt="image.png" title="image.png"></p><h2>业务层记录MyBatis底层SQL会话时长</h2><h3>业务描述</h3><p>现在有一个业务,需要记录数据持久层api方法调用时执行时长,如何实现?<br>要求:<br>1)我们不能直接将日志记录写到单元测试类中。<br>2)我们不能修改数据持久层实现类。</p><h3>API架构设计</h3><p>基于日志记录业务进行API设计,如图所示:</p><p><img src="/img/bVbRL0L" alt="image.png" title="image.png"></p><h3>业务时序图分析</h3><p>商品查询并进行日志记录,其运行时序分析,如图所示:</p><p><img src="/img/bVbRL0S" alt="image.png" title="image.png"></p><h3>业务代码设计及实现</h3><p>第一步:定义GoodsService接口,代码如下:</p><pre><code>package com.cy.pj.goods.service;
import com.cy.pj.goods.pojo.Goods;
import java.util.List;
public interface GoodsService {
List<Goods> findGoods();
}</code></pre><p>第二步:定义GoodsServiceImpl实现类并进行日志记录,代码如下:</p><pre><code>package com.cy.pj.goods.service;
import com.cy.pj.goods.dao.GoodsDao;
import com.cy.pj.goods.pojo.Goods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GoodsServiceImpl implements GoodsService{
private static final Logger log=
LoggerFactory.getLogger(GoodsServiceImpl.class);
@Autowired
private GoodsDao goodsDao;
@Override
public List<Goods> findGoods() {
long t1=System.currentTimeMillis();
List<Goods> list=goodsDao.findGoods();
long t2=System.currentTimeMillis();
log.info("findGoods()->t2-t1={}",t2-t1);//日志输出
return list;
}
}</code></pre><p>日志API应用分析,如图所示:</p><p><img src="/img/bVbRMhK" alt="image.png" title="image.png"></p><h3>测试代码的编写及运行</h3><p>编写单元测试类GoodsServiceTests,对GoodsService对象方法进行单元测试,例如:</p><pre><code>package com.cy.pj.goods.service;
import com.cy.pj.goods.pojo.Goods;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class GoodsServiceTests {
@Autowired
private GoodsService goodsService;
@Test
void testFindGoods(){
List<Goods> list=goodsService.findGoods();
for(Goods g:list){
System.out.println(g);
}
}
}</code></pre><h3>测试运行过程中的BUG分析</h3><ul><li>NullPointerException,如图所示:</li></ul><p><img src="/img/bVbRMhw" alt="image.png" title="image.png"></p><ul><li>NoSuchBeanDefinitionException,如图所示:</li></ul><p><img src="/img/bVbRMhC" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节主要是对MyBatis框架做了一个基本介绍,并且基于springboot工程做了mybatis框架的整合实现,重点掌握其应用架构,代码结构,编写过程、运行过程以及在实现过程中的问题分析和对应的解决方案。</p>
03-基于IDEA创建SpringBoot项目并进行入门分析
https://segmentfault.com/a/1190000025138022
2020-09-27T12:23:04+08:00
2020-09-27T12:23:04+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
235
<h2>SpringBoot 项目创建</h2><h3>创建Module</h3><p>基于IDEA创建项目Module,模块名为04-springboot-start,组id和包名为com.cy,如图所示:<br><img src="/img/bVbRDeo" alt="image.png" title="image.png"><br>填写module信息,如图所示:<br><img src="/img/bVbRDeB" alt="image.png" title="image.png"><br>选择项目module版本,暂时不需要自己手动添加任何依赖,如图所示:<br><img src="/img/bVbRDe2" alt="image.png" title="image.png"><br>填写Module名称,完成module创建,如图所示<br><img src="/img/bVbRDgw" alt="image.png" title="image.png"></p><h3>项目结构分析</h3><p>项目Module创建好以后,其代码结构分析,如图所示:</p><p><img src="/img/bVbRDwd" alt="image.png" title="image.png"></p><h2>SpringBoot 项目启动分析</h2><h3>启动入口</h3><p>SpringBoot 工程中由SpringBootApplication注解描述的类为启动入口类,例如:</p><pre><code>package com.cy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {//Application.class
public static void main(String[] args) {//Main Thread
SpringApplication.run(Application.class, args);
}
}</code></pre><h3>启动过程概要分析</h3><p>SpringBoot工程启动时其简易初始化过程,如图所示:</p><p><img src="/img/bVbRDv9" alt="image.png" title="image.png"></p><p>在启动过程中底层做了哪些事情,大致描述如下:<br>1)基于配置加载类(通过ClassLoader将指定位置的类读到内存->底层通过线程调用IO从磁盘读取到内存)。<br>2)对类进行分析(创建字节码对象-Class类型,通过反射获取器配置信息)。<br>3)对于指定配置(例如由spring特定注解描述)的对象存储其配置信息(借助BeanDefinition对象存储)。<br>4)基于BeanDefinition对象中class的配置构建类的实例(Bean对象),并进行bean对象的管理(可能会存储到bean池)。</p><h2>SpringBoot 快速入门分析</h2><h3>业务描述</h3><p>在项目Module中定义一个类,类名为DefaultCache,然后将此类对象交给Spring创建并管理。最后通过单元测试对类的实例进行分析。</p><h3>API设计分析</h3><p>基于业务描述,进行API及关系设计,如图所示:</p><p><img src="/img/bVbRDvT" alt="image.png" title="image.png"></p><h3>代码编写及运行</h3><p>基于业务及API设计,进行代码编写,其过程如下:</p><p>第一步:定义DefaultCache类</p><pre><code>package com.cy.pj.common.cache;
import org.springframework.stereotype.Component;
/**
* @Component 注解描述的类,表示此类交给Spring框架管理。
*/
@Component
public class DefaultCache {
}</code></pre><p>第二步:定义DefaultCacheTests单元测试类</p><pre><code>package com.cy.pj.common.cache;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@SpringBootTest
public class DefaultCacheTests {
/**
* @Autowired 注解描述的属性由spring框架按照一定规则为其注入值(赋值)
* 赋值过程是怎样的?
* 1)依赖查找?(请问查找规则是什么?)
* 2)依赖注入?(需要借助什么技术?)
*/ @Autowired
private DefaultCache defaultCache;
@Test
void testDefaultCache(){
System.out.println(defaultCache.toString());
//FAQ? defaultCache变量引用的对象是由谁创建的,存储 到了哪里?bean pool
}
}</code></pre><p>第三步:运行单元测试类进行应用分析<br>启动运行单元测试方法,检测其输出结果,基于结果分析:<br>1)SpringBoot项目中Bean对象的构建。<br>2)SpringBoot项目中Bean对象的获取。</p><h3>运行过程中的BUG分析</h3><ul><li>Bean类型找不到,如图所示:</li></ul><p><img src="/img/bVbRDGR" alt="image.png" title="image.png"></p><ul><li>空指针异常(NullPointerExcetpion-NPE),如图所示:</li></ul><p><img src="/img/bVbRDG7" alt="image.png" title="image.png"></p><ul><li>启动类找不到,如图所示:</li></ul><p><img src="/img/bVcKUka" alt="image.png" title="image.png"></p><ul><li>启动类有多个,如图所示:</li></ul><p><img src="/img/bVcKUkj" alt="image.png" title="image.png"></p><ul><li>NoSuchBeanDefinition异常,如图所示:</li></ul><p><img src="/img/bVcKUku" alt="image.png" title="image.png"></p><ul><li>单元测试类中的方法添加了参数,如图所示:</li></ul><p><img src="/img/bVcKUky" alt="image.png" title="image.png"></p><h2>SpringBoot 项目中的对象特性分析</h2><h3>准备工作</h3><p>第一步:创建项目Module,例如名字为05-springboot-features,如图所示:<br><img src="/img/bVbREe9" alt="image.png" title="image.png"></p><p>第二步:添加业务类ObjectPool,代码如下:</p><pre><code>package com.cy.pj.common.pool;
@Component
public class ObjectPool{//假设此对象为一个对象池
public ObjectPool(){//假设运行项目启动类,此构造方法执行了,说明此类对象构建了。
Systemd.out.println("ObjectPool()")
}
}</code></pre><p>思考:一般池对象有什么特点?<br>1)在JVM内存会开辟一块相对比较大的空间。<br>2)在这块空间中存储一些对象(思考基于什么存储结构进行存储-数组,链表,散列表)。<br>3)基于“享元模式”设计思想,实现内存中对象的可重用性。</p><p>第三步:定义单元测试,代码如下:</p><pre><code>package com.cy.pj.pool;
import com.cy.pj.common.pool.ObjectPool;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ObjectPoolTests {
@Autowired
private ObjectPool objectPool01;
@Autowired
private ObjectPool objectPool02;
@Test
void testObjectPool01(){
System.out.println(objectPool01==objectPool02);
}
}</code></pre><h3>延迟加载</h3><p>现在思考一个问题,对于ObjectPool这个类,假如项目启动以后,暂时不会用到这个池对象,是否有必要对其进行创建(默认是会创建的)?我们知道没必要,因为占用内存。那如何在启动时不创建此类对象呢?借助Spring框架提供的延迟加载特性进行实现。例如,我们可以在需要延迟加载的类上使用@Lazy注解进行描述,代码如下:</p><pre><code>package com.cy.pj.common.pool;
@Lazy
@Component
public class ObjectPool{//假设此对象为一个对象池
public ObjectPool(){//假设运行项目启动类,此构造方法执行了,说明此类对象构建了。
Systemd.out.println("ObjectPool()")
}
}</code></pre><p>此时,我们再去运行运行启动类,检测ObjectPool对象是否创建了,假如没有创建,说明延迟加载生效了。此时,我们总结一下,什么对象适合使用延迟加载特性呢?大对象,稀少用(项目启动以后,暂时用不到)的对象。<br>注意:延迟加载并不是延迟对类进行加载,而是在启动时,暂时不创建类的实例。假如想看一下内存中的类是否被加载了,可以通过JVM参数进行检测,参数为-XX:+TraceClassLoading。</p><h3>对象作用域分析</h3><p>在实际的项目中内存中的对象有一些可能要反复应用很多次,有一些可能用完以后再也不用了或者说应用次数很少了。对于经常要重复使用的对象我可考虑存储到池中(例如交给spring框架进行管理),应用次数很少的对象那就没必要放到池中了,用完以后让它自己销毁就可以了。在Spring项目工程中为了对这样的对象进行设计和管理,提供了作用域特性的支持,具体应用:</p><pre><code>package com.cy.pj.common.pool;
@Scope("singleton")
@Lazy
@Component
public class ObjectPool{//假设此对象为一个对象池
public ObjectPool(){//假设运行项目启动类,此构造方法执行了,说明此类对象构建了。
Systemd.out.println("ObjectPool()")
}
}</code></pre><p>其中,在上面的代码中,我们使用了@Scope注解对类进行描述,用于指定类的实例作用域。不写@Scope默认就是单例(singleton)作用域,这个作用域会配合延迟加载(@Lazy)特性使用,表示此类的实例在需要时可以创建一份并且将其存储到spring的容器中(Bean池),需要的时候从池中取,以实现对象的可重用。假如一些对象应用次数非常少,可以考虑不放入池中,进而使用@Scope("prototype")作用域对类进行描述,让此类的对象何时需要何时创建,用完以后,当此对象不可达了,则可以直接被GC系统销毁。</p><h3>对象生命周期方法</h3><p>程序中的每个对象都有生命周期,对象创建,初始化,应用,销毁的这个过程称之为对象的生命周期。在对象创建以后要初始化,应用完成以后要销毁时执行的一些方法,我们可以称之为生命周期方法。但不见得每个对象都会定义生命周期方法。在实际项目中往往一些池对象通常会定义这样的一些生命周期方法(例如连接池)。那这样的方法在spring工程中如何进行标识呢?通常要借助@PostConstruct和@PreDestroy注解对特定方法进行描述,例如:</p><pre><code>package com.cy.pj.common.pool;
@Scope("singleton")
@Lazy
@Component
public class ObjectPool{//假设此对象为一个对象池
public ObjectPool(){
Systemd.out.println("ObjectPool()")
}
@PostConstruct
public void init(){
System.out.println("init()");
}
@PreDestroy
public void destory(){
System.out.println("destory()");
}
}</code></pre><p>其中:<br>1)@PostConstruct 注解描述的方法为生命周期初始化方法,在对象构建以后执行.<br>2)@PreDestroy 注解描述的方法为生命周期销毁方法,此方法所在的对象,假如存储到了spring容器,那这个对象在从spring容器移除之前会先执行这个生命周期销毁方法(prototype作用域对象不执行此方法).</p><h2>SpringBoot 项目中的依赖注入过程分析</h2><p>在SpringBoot工程中,假如类与类之间存在着一定的依赖关系,Spring是如何进行依赖注入的呢,现在我们就通过一个案例做一个分析。</p><h3>准备工作</h3><p>第一步:创建一个项目module,如图所示:<br><img src="/img/bVbRFlw" alt="image.png" title="image.png"><br>第二步:启动运行项目,检测是否能成功启动</p><h3>案例设计及分析</h3><p>为了更好理解spring框架的底层注入机制,现在进行案例API设计,如图所示:</p><p><img src="/img/bVbRFmC" alt="image.png" title="image.png"></p><p>在这个案例中单元测试类CacheTests中定义一个Cache接口类型的属性,然后由Spring框架完成对cache类型属性值的注入。</p><h3>代码编写及测试分析</h3><p>第一步:定义Cache接口,代码如下:</p><pre><code>package com.cy.pj.common.cache;
public interface Cache {
}</code></pre><p>第二步:定义Cache接口实现类SoftCache,代码如下:</p><pre><code>package com.cy.pj.common.cache;
@Component
public class SoftCache implements Cache{
}</code></pre><p>第三步:定义Cache接口实现类WeakCache,代码如下:</p><pre><code>package com.cy.pj.common.cache;
@Component
public class WeakCache implements Cache{
}</code></pre><p>第四步:定义CacheTests单元测试类,代码如下:</p><pre><code>package com.cy.pj.common.cache;
import org.junit.jupiter.api.Test;
@SpringBootTest
public class CacheTests {
@Autowired
@Qualifier("softCache")
private Cache cache;
@Test
public void testCache() {
System.out.println(cache);
}
}
</code></pre><p>其中,@Autowired由spring框架定义,用于描述类中属性或相关方法(例如构造方法)。Spring框架在项目运行时假如发现由他管理的Bean对象中有使用@Autowired注解描述的属性或方法,可以按照指定规则为属性赋值(DI)。其基本规则是:首先要检测容器中是否有与属性或方法参数类型相匹配的对象,假如有并且只有一个则直接注入。其次,假如检测到有多个,还会按照@Autowired描述的属性或方法参数名查找是否有名字匹配的对象,有则直接注入,没有则抛出异常。最后,假如我们有明确要求,必须要注入类型为指定类型,名字为指定名字的对象还可以使用@Qualifier注解对其属性或参数进行描述(此注解必须配合@Autowired注解使用)。</p><p>第五步:运行CacheTests检测输出结果,基于结果理解其注入规则。</p><h3>编写及测试过程中的BUG分析</h3><ul><li>依赖注入异常,如图所示:</li></ul><p><img src="/img/bVbRFox" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节为springboot技术入门章节,主要讲述了SpringBoot工程下,spring中bean对象的编写,特性以及依赖注入的规则,希望通过这一小节的讲解,同学们能够理解我们为什么要将对象交给spring管理,spring管理对象有什么优势,我们在springboot工程中应该如何配置这些对象。</p>
02-IDE工具之IDEA中Module的创建、删除、导入。
https://segmentfault.com/a/1190000024577907
2020-09-24T23:34:46+08:00
2020-09-24T23:34:46+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
129
<h2>创建项目Module并运行</h2><h3>创建并运行java module</h3><p>在IDEA打开的项目中创建Java Module,如图所示:</p><p><img src="/img/bVbPhjp" alt="image.png" title="image.png"></p><p>在创建Java Module的界面,选择Next,输入module名,如图所示:</p><p><img src="/img/bVbPhmB" alt="image.png" title="image.png"></p><p>Java Module创建好以后的结构,如图所示:</p><p><img src="/img/bVbPhkv" alt="image.png" title="image.png"></p><p>在项目模块01-javase中创建包,例如:</p><p><img src="/img/bVbPhm9" alt="image.png" title="image.png"></p><p>在指定包中创建类,如图所示:</p><p><img src="/img/bVbPhnG" alt="image.png" title="image.png"></p><p>运行Java类,如图所示:</p><p><img src="/img/bVbPho4" alt="image.png" title="image.png"></p><h3>创建并运行Maven Module</h3><p>在项目中,创建maven module,如图所示:</p><p><img src="/img/bVbPhse" alt="image.png" title="image.png"></p><p>接下来,输入module基本信息,如图所示:</p><p><img src="/img/bVbPhtl" alt="image.png" title="image.png"></p><p>打开pom.xml文件,添加Junit依赖,如图所示:</p><p><img src="/img/bVbPhuE" alt="image.png" title="image.png"></p><p>创建包、单元测试类和方法,进行单元测试,如图所示:</p><p><img src="/img/bVbPhu6" alt="image.png" title="image.png"></p><h3>创建并运行Spring Initializr Module</h3><p>在项目中,创建Spring Initializr Module,如图所示:</p><p><img src="/img/bVbPhvR" alt="image.png" title="image.png"></p><p>接下来输入Spring Initializr Module信息,如图所示:</p><p><img src="/img/bVbPhys" alt="image.png" title="image.png"></p><p>选择springboot版本,依赖,然后进入一下,如图所示:</p><p><img src="/img/bVbPhyJ" alt="image.png" title="image.png"></p><p>输入module信息,然后完成Module的创建,如图所示:</p><p><img src="/img/bVbPhyX" alt="image.png" title="image.png"></p><p><img src="/img/bVbPhy4" alt="image.png" title="image.png"></p><p>Spring Initializr Module创建好以后,其结构如图所示:<br><img src="/img/bVbPhy5" alt="image.png" title="image.png"></p><p>Module创建好以后,可以打开pom.xml文件,修改其springboot版本,如图所示:<br><img src="/img/bVbPhzc" alt="image.png" title="image.png"></p><p>SpringBoot 版本修改好以后(可选),向pom.xml右键,选择Add as Maven Project 选项,进行maven项目构建(此时才会springboot 依赖下载和编译),例如:</p><p><img src="/img/bVbPhz0" alt="image.png" title="image.png"></p><p>Module 构建完成,打开启动类运行module,如图所示:</p><p><img src="/img/bVbPhAA" alt="image.png" title="image.png"></p><p>Module成功启动和运行结果,如图所示:</p><p><img src="/img/bVbPhAw" alt="image.png" title="image.png"></p><h2>IDEA中项目的其它操作</h2><h3>从IDEA删除项目module?</h3><ol><li>首先打开项目结构(Project Structure),找到Moudles菜单项,选中某个moudle,点击"-"符号,移除项目.</li><li>然后在IDEA中,在具体的项目模块上,右键选择delete执行项目module删除操作.</li></ol><h3>从IDEA中导入项目Module?</h3><ol><li>首先打开项目结构(Project Structure),找到Moudles菜单项,选中某个moudle,点击"+"符号.</li><li>找到对应的项目,然后执行导入(import)操作.</li></ol><h3>从IDEA中load/unload 项目modules?</h3><p>直接选择项目module,然后右键执行 load/upload操作即可,这个动作类似eclipse中的close操作</p><h2>总结(Summary)</h2><p>本章节中讲解了IDEA工具下Java Module,Maven Module,Spring Boot Module 的创建,运行,删除,导入等操作,通过这些操作掌握IDEA中对项目的一个基本操作。</p>
01-IDE工具之IDEA的简介、下载与安装、初步配置。
https://segmentfault.com/a/1190000024573669
2020-09-24T16:37:55+08:00
2020-09-24T16:37:55+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
196
<h2>IDEA简介</h2><h3>概述</h3><p>IDEA全称是IntelliJ,是JetBrains公司推出一个集成开发工具,是Java开发工具中的翘楚,基于这个开发工具可以快速开发我们的Java相关项目。相对于其它开发工具,IDEA提供了更加强大的提示功能,全面的快捷键操作,模板代码以及快速的资源整合。</p><h3>下载与安装</h3><h4>准备工作</h4><ul><li>硬件(Hardware)要求<p>1)内存8G或以上<br>2)硬盘磁盘空闲空间大于5GB,最好有一块固态硬盘。</p></li><li>软件(Software)要求<p>1)Microsoft Windows 8 or later<br>2)macOS 10.13 or later<br>3)Any Linux distribution <br>4)Set Up and Config JDK1.8</p></li></ul><h4>IDEA工具下载</h4><p>IDEA官网下载地址如下:</p><pre><code>https://www.jetbrains.com/idea/</code></pre><p>基于操作系统的不同,可以选择不同平台下的IDEA,例如:<br><img src="/img/bVbO96e" alt="image.png" title="image.png"></p><p>说明:至于社区版(功能受限的免费版)/还是商业版(可试用)自行决定。</p><h4>IDEA工具安装</h4><p>IDEA 下载以后需要进行安装进行使用。假如只是下载的解压版本,则可以将其拷贝到非中文目录,然后直接解压进行应用。假如下载的可执行程序(例如windows平台下的.exe文件)则直接启动可执行程序,然后按照提示,一步一步安装即可。</p><p>IDEA官方帮助文档文档参考如下:</p><pre><code>https://www.jetbrains.com/help/idea/discover-intellij-idea.html</code></pre><h4>IDEA工具启动</h4><p>找到解压或安装好的idea根目录,然后进入bin目录找到idea.bat或者idea64.exe文件,然后直接双击进行启动,第一个启动欢迎界面如图所示:</p><p><img src="/img/bVbPgkd" alt="image.png" title="image.png"></p><p>在第一个界面呈现过程中,其实是IDEA底层在进行启动初始化,假如是第一次启动,稍等片刻会进入第二个界面,例如:</p><p><img src="/img/bVbPgl7" alt="image.png" title="image.png"></p><p>在第二个界面中,我们先选择创建新项目(New Project),然后进入创建项目的界面,这里我们先选择创建空项目,如图所示:</p><p><img src="/img/bVbPgnW" alt="image.png" title="image.png"></p><p>选择好Empty Project以后,点击next进入项目的设计,如图所示:</p><p><img src="/img/bVbPgtp" alt="image.png" title="image.png"></p><p>在上面图中,点击Finish按钮完成项目的创建,进入IDEA的项目工作窗口,如图所示:</p><p><img src="/img/bVbPgvC" alt="image.png" title="image.png"></p><h2>IDEA 基本配置</h2><h3>Appearance(显示)主题配置</h3><p><img src="/img/bVbPbts" alt="image.png" title="image.png"></p><h3>KeyMap(快捷键)配置</h3><p><img src="/img/bVbPbDo" alt="image.png" title="image.png"></p><h3>Editor(编辑区)配置</h3><h4>General 配置</h4><p>滚轮滑动,改变字体大小配置,如图所示:<br><img src="/img/bVbPbuq" alt="image.png" title="image.png"><br>包导入配置,如图所示:<br><img src="/img/bVbPbwZ" alt="image.png" title="image.png"><br>行号与方法分隔符配置,如图所示:<br><img src="/img/bVbPbxg" alt="image.png" title="image.png"><br>代码提示配置,如图所示:<br><img src="/img/bVbPbxm" alt="image.png" title="image.png"><br>代码补全配置,如图所示:<br><img src="/img/bVbPgG2" alt="image.png" title="image.png"></p><h4>Font 配置</h4><p>代码编辑区字体配置,如图所示:<br><img src="/img/bVbPbxw" alt="image.png" title="image.png"><br>控制台字体配置,如图所示:<br><img src="/img/bVbPbxI" alt="image.png" title="image.png"></p><h4>Color 配置</h4><p>代码中的注释颜色配置,如图所示:<br><img src="/img/bVbPbyL" alt="image.png" title="image.png"></p><h4>File 编码配置</h4><p><img src="/img/bVbPbBH" alt="image.png" title="image.png"></p><h3>Build 执行配置</h3><h4>Compiler(编译配置)</h4><p>自动编译配置(可选),选上了可能会比较慢,如图所示:<br><img src="/img/bVbPbCN" alt="image.png" title="image.png"></p><h4>Build Tools 中的Maven配置</h4><blockquote>Maven 初始化配置(假如已有配置好的,可以使用已有的配置)</blockquote><p>找到你磁盘中的maven根目录下的conf目录,并打开conf目录下的settings.xml文件,然后进行如下配置:</p><ul><li>配置maven本地库(从maven远程服务器下载的资源存储到的位置)</li></ul><pre><code><localRepository>${user.home}/.m5/repository</localRepository></code></pre><ul><li>配置maven私服(配置到mirrors标签内部)。</li></ul><pre><code> <mirror>
<id>aliyun</id>
<name>aliyun for maven</name>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror></code></pre><ul><li>配置maven中的profile(配置到profiles标签内部),设置JDK编译和运行版本。</li></ul><pre><code> <profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8
</maven.compiler.compilerVersion>
</properties>
</profile></code></pre><blockquote>IDEA 中集成Maven配置</blockquote><p>找到Build Tools/Maven选项,然后在IDEA中集成本地maven配置。如图所示:</p><p><img src="/img/bVbPcrP" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节主要是对IDEA开发工具进行了一个简单介绍,然后基于我们后续要做的应用,做了一个初步配置,基于这个配置我们后续可以更好的展开项目的创建、测试等。</p>
13-SpringBoot工程下活动(Activity)模块设计及实现?
https://segmentfault.com/a/1190000023899990
2020-09-05T08:48:00+08:00
2020-09-05T08:48:00+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
108
<h2>业务描述</h2><p>基于Spring,MyBatis,SpringBoot,Thymeleaf,Ajax技术实现活动模块的查询,添加等操作。</p><h2>项目环境初始化</h2><h3>准备工作</h3><pre><code>1. MySQL(5.7)
2. JDK (1.8)
3. Maven (3.6.3)
4. STS(4.7.1)</code></pre><h3>数据库初始化</h3><p>打开mysql控制台,然后按如下步骤执行goods.sql文件。 <br>第一步:登录mysql。</p><pre><code>mysql –uroot –proot</code></pre><p>第二步:设置控制台编码方式。</p><pre><code>set names utf8;</code></pre><p>第三步:执行activity.sql文件(切记不要打开文件复制到mysql客户端运行)。</p><pre><code>source d:/activity.sql</code></pre><p>其中activity.sql文件内容如下:</p><pre><code>drop database if exists dbactivity;
create database dbactivity default character set utf8;
use dbactivity;
create table tb_activity(
id bigint primary key auto_increment,
title varchar(100) not null,
category varchar(100) not null,
startTime datetime not null,
endTime datetime not null,
remark text,
state tinyint,
createdTime datetime not null,
createdUser varchar(100)
)engine=InnoDB;
insert into tb_activity values (null,'ABS','cuoxiao','2020-02-02 15:00:00','2020-02-03 15:00:00','...',1,now(),'xiaoli');
insert into tb_activity values (null,'VALIDATE','cuoxiao','2020-02-02 15:00:00','2020-02-03 15:00:00','...',1,now(),'xiaoli');
insert into tb_activity values (null,'VALIDATE','cuoxiao','2020-02-02 15:00:00','2020-02-03 15:00:00','...',1,now(),'xiaoli');
insert into tb_activity values (null,'ABS','cuoxiao','2020-02-02 15:00:00','2020-02-03 15:00:00','...',1,now(),'xiaoli');
insert into tb_activity values (null,'ACCESS','cuoxiao','2020-02-02 15:00:00','2020-02-03 15:00:00','...',1,now(),'xiaoli');</code></pre><h3>创建项目并添加依赖</h3><p>第一步:基于start.spring.io 创建项目并设置基本信息。</p><p><img src="/img/bVbMrCb" alt="image.png" title="image.png"></p><p>第二步:创建项目时指定项目核心依赖。<br><img src="/img/bVbMrCe" alt="image.png" title="image.png"></p><p>第三步:项目创建以后分析其结构。</p><p><img src="/img/bVbMrC4" alt="image.png" title="image.png"></p><h3>项目配置文件内容初始化</h3><pre><code>#server
server.port=80
#server.servlet.context-path=/
#spring datasource
spring.datasource.url=jdbc:mysql:///dbactivity?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#spring mybatis
mybatis.mapper-locations=classpath:/mapper/*/*.xml
#spring logging
logging.level.com.cy=debug
#spring thymeleaf
spring.thymeleaf.prefix=classpath:/templates/modules/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false</code></pre><h3>项目API架构设计</h3><p>其API架构设计,如图所示:<br><img src="/img/bVbMrC6" alt="image.png" title="image.png"></p><h2>活动模块查询业务实现</h2><h3>业务描述</h3><p>从数据库查询所有活动(Activity)信息,然后呈现在页面上(基于JS方式进行呈现),如图所示:</p><p><img src="/img/bVbMxFr" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>在浏览器地址栏输入查询活动信息的url获取活动信息,其时序过程如图所示:</p><p><img src="/img/bVbMxKt" alt="image.png" title="image.png"></p><h3>POJO类设计及实现</h3><p>创建Activity类,基于此类对象封装从数据库获取的活动信息,代码如下:</p><pre><code>package com.cy.pj.activity.pojo;
import java.util.Date;
import lombok.Data;
@Data
public class Activity {
private Integer id;
private String title;
private String category;
private Date startTime;
private Date endTime;
private String remark;
private Integer state;
private Date createdTime;
private String createdUser;
}</code></pre><blockquote>其中,@Data注解为Lombok注解,需要在IDE环境中已经安装lombok插件,项目中添加lombok依赖,假如没有也可以自己添加set/get相关方法</blockquote><h3>Dao接口方法及映射定义</h3><p>定义活动模块数据层接口及查询方法,代码如下:</p><pre><code>package com.cy.pj.activity.dao;
import com.cy.pj.activity.pojo.Activity;
@Mapper
public interface ActivityDao {
@Select("select * from tb_activity order by createdTime desc")
List<Activity> findActivitys();
}</code></pre><h3>Service接口方法定义及实现</h3><p>定义service接口以及获取活动信息的方法,代码如下:</p><pre><code>package com.cy.pj.activity.service;
import java.util.List;
import com.cy.pj.activity.pojo.Activity;
//引入包中的类:ctrl+shift+o
public interface ActivityService {
List<Activity> findActivitys();
}</code></pre><p>定义service接口实现类,并重写接口方法,代码如下:</p><pre><code>package com.cy.pj.activity.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cy.pj.activity.dao.ActivityDao;
import com.cy.pj.activity.pojo.Activity;
import com.cy.pj.activity.service.ActivityService;
@Service
public class ActivityServiceImpl implements ActivityService {
@Autowired
private ActivityDao activityDao;
@Override
public List<Activity> findActivitys() {
return activityDao.findActivitys();
}
}</code></pre><h3>Controller方法定义及实现</h3><pre><code>package com.cy.pj.activity.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.cy.pj.activity.pojo.Activity;
import com.cy.pj.activity.service.ActivityService;
@Controller
public class ActivityController {
@Autowired
private ActivityService activityService;
@RequestMapping("/activity/doActivityUI")
public String doActivityUI(){
return "activity";
}
/**查询所有活动信息*/
@RequestMapping("/activity/doFindActivitys")
@ResponseBody
public List<Activity> doFindActivitys() {
List<Activity> list=activityService.findActivitys();
return list;
}
}
</code></pre><h3>Activity 列表页面设计及实现</h3><p>本次页面样式基于bootstrap(一个前端框架,官网为bootcss.com)进行实现,首先在项目工程中添加静态资源,如图所示:</p><p><img src="/img/bVbMoci" alt="image.png" title="image.png"><br>设计activity.html页面,引入bootstrap,jquery等相关资源,如图所示:</p><p><img src="/img/bVbMocj" alt="image.png" title="image.png"></p><p>在activity.html页面中添加呈现Activity数据的html元素:</p><pre><code><table class="table">
<thead>
<tr>
<th>title</th>
<th>Category</th>
<th>StartTime</th>
<th>EndTime</th>
<th>State</th>
<th>Operation</th>
</tr>
</thead>
<tbody id="tbodyId">
<tr><td colspan="6">数据正在加载中.......</td></tr>
</tbody>
</table></code></pre><p>基于Ajax异步加载服务端活动数据并进行局部刷新</p><pre><code><script type="text/javascript">
function doFindActivitys(){
var url="/activity/doFindActivitys"
//启动ajax技术,基于GET请求方式获取服务端json数据
//getJSON函数默认会将服务端返回的json串转换为js对象
$.getJSON(url,function(result){
var tBody=$("#tbodyId");
tBody.empty();//清空原有body内容
for(var i=0;i<result.length;i++){//循环一次,迭代一行
//构建当前行对象
var tr=`<tr>
<td>${result[i].title}</td>
<td>${result[i].category}</td>
<td>${result[i].startTime}</td>
<td>${result[i].endTime}</td>
<td>${result[i].state==1?'有效':'无效'}</td>
<td><button type='button' class='btn btn-danger btn-sm'>delete</button></td>
</tr>`
//将每一行的内容都追加tbody中
tBody.append(tr);
}
});
};
doFindActivitys();
</script></code></pre><h3>启动tomcat服务器进行访问测试分析</h3><p>启动项目,在浏览器中输入activity页面的访问地址,呈现活动信息,如图所示:</p><p><img src="/img/bVbMxFr" alt="image.png" title="image.png"></p><p>客户端断点调试分析,如图所示:</p><p><img src="/img/bVbMAsC" alt="image.png" title="image.png"></p><p>运行时断点设置,如图所示:</p><p><img src="/img/bVbMAsO" alt="image.png" title="image.png"></p><p>进入断点方法内部,如图所示:</p><p><img src="/img/bVbMAsS" alt="image.png" title="image.png"></p><p>检查响应数据在客户端的呈现,如图所示:</p><p><img src="/img/bVbMAsW" alt="image.png" title="image.png"></p><h3>项目启动及运行过程中的BUG分析</h3><ul><li>数据库链接的url配置错误,如图所示:</li></ul><p><img src="/img/bVbMocp" alt="image.png" title="image.png"></p><ul><li>空指针异常,如图所示:</li></ul><p><img src="/img/bVbMocr" alt="image.png" title="image.png"></p><ul><li>JS 引入错误,如图所示:</li></ul><p><img src="/img/bVbMxKQ" alt="image.png" title="image.png"></p><p><img src="/img/bVbMxKY" alt="image.png" title="image.png"></p><p><img src="/img/bVbMxK4" alt="image.png" title="image.png"></p><ul><li>JS 语法错误,如图所示:</li></ul><p><img src="/img/bVbMxLb" alt="image.png" title="image.png"></p><h2>活动模块添加业务实现</h2><h3>业务描述</h3><p>在活动列表页面,设计添加按钮,点击添加按钮时弹出模态框,在模态框中呈现活动添加表单元素,其添加表单原型设计,<br>如图所示:<br><img src="/img/bVbMrld" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>活动添加业务,其时序分析,如图所示:</p><p><img src="/img/bVbMxM0" alt="image.png" title="image.png"></p><h3>Dao接口方法及映射定义</h3><p>在ActivityDao接口中添加持久化活动信息的方法,代码如下:</p><pre><code>int insertObject(Activity activity);</code></pre><p>在ActivityMapper映射文件中添加映射元素,代码如下:</p><pre><code> <insert id="insertObject" parameterType="com.cy.pj.activity.pojo.Activity"
useGeneratedKeys="true" keyProperty="id">
insert into tb_activity
(title,category,startTime,endTime,remark,state,createdUser,createdTime)
values
(#{title},#{category},#{startTime},#{endTime},
#{remark},#{state},#{createdUser},now())
</insert></code></pre><p>"#{}"表达式数据获取分析,如图所示:</p><p><img src="/img/bVbMAs6" alt="image.png" title="image.png"></p><p>执行insert操作时获取数据库主键id值,如图所示:</p><p><img src="/img/bVbMxOl" alt="image.png" title="image.png"></p><p>当然,也可以以注解的方式进行SQL映射,如图所示:</p><p><img src="/img/bVbMrlF" alt="image.png" title="image.png"></p><p>当活动创建好以后,假如到了结束时间,动态更新活动的状态,代码如下:</p><pre><code>@Update("update tb_activity set state=0 where id=#{id}")
int updateState(Long id);</code></pre><h3>Service接口方法定义及实现</h3><p>在ActivityService接口中,添加保存活动信息的方法,代码如下:</p><pre><code>int saveActivity(Activity entity);</code></pre><p>在ActivityServiceImpl实现类中添加,接口方法的实现,代码如下:</p><pre><code>@Override
public int saveActivity(Activity entity) {
int rows=activityDao.insertObject(entity);
Timer timer=new Timer();
//此对象可以负责去执行一些任务(这个对象内置一个线程和一个任务队列)
//启动线程执行任务调度
timer.schedule(new TimerTask() {//TimerTask为任务
@Override
public void run() {
//一旦调用此任务的线程获得了CPU就会执行这个任务的run方法
activityDao.updateState(entity.getId());
timer.cancel();//退出任务调度(后续线程也会销毁)
}
}, entity.getEndTime());//按指定时间执行任务.
return rows;
}</code></pre><h3>Controller方法定义及实现</h3><p>在ActivityController中添加,处理添加请求的方法,代码如下:</p><pre><code>@RequestMapping("/activity/doSaveActivity")
@ResponseBody
public String doSaveActivity(Activity activity) {
activityService.saveActivity(activity);
return "save ok";
}
</code></pre><h3>Activity 页面添加表单设计及实现</h3><p>在activity.html页面中添加表单元素,用于实现与用户交互,首先在activity页面上添加一个添加按钮,代码如下:</p><pre><code><button type="button" class="btn btn-primary btn-sm"
data-toggle="modal" data-target="#myModal">创建活动
</button></code></pre><p>在指定位置添加模态框(可参考bootcss.com),代码如下:</p><pre><code><div class="modal fade" id="myModal" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel">创建活动</h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="doSaveObject()">
Save Changes</button>
</div>
</div>
</div>
</div>
</code></pre><p>在模态框”modal-body”位置,添加form表单,代码如下:</p><pre><code><form class="form-horizontal" th:action="@{/activity/doSaveActivity}" method="post">
<div class="form-group">
<label for="titleId" class="col-sm-2 control-label">标题</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="title" id="titleId" placeholder="title">
</div>
</div>
<div class="form-group">
<label for="categoryId" class="col-sm-2 control-label">类型</label>
<div class="col-sm-10">
<select id="categoryId" name="category" class="form-control">
<option value="教育培训">教育培训</option>
<option value="企业活动">企业活动</option>
<option value="交友活动">交友活动</option>
</select>
</div>
</div>
<div class="form-group" >
<label for="startTimeId" class="col-sm-2 control-label">开始时间</label>
<div class="col-sm-10">
<input type="text" class="form-control form_datetime"
name="startTime" id="startTimeId" placeholder="start time">
</div>
</div>
<div class="form-group">
<label for="endTimeId" class="col-sm-2 control-label">结束时间</label>
<div class="col-sm-10">
<input type="text" class="form-control form_datetime"
name="endTime" id="endTimeId" placeholder="end time">
</div>
</div>
<div class="form-group">
<label for="remarkId" class="col-sm-2 control-label">备注</label>
<div class="col-sm-10">
<textarea type="text" class="form-control" rows="5" name="remark" id="remarkId">
</textarea>
</div>
</div>
</form>
</code></pre><p>添加JS代码,处理模态框Save按钮事件,代码如下:</p><pre><code>function doSaveObject(){
//1.定义url
let url="/activity/doSaveObject";
//2.定义请求参数
var params=
$("#saveFormId").serialize();//serialize()为jquery中直接获取表单数据方法
console.log("params",params);
//3.发送异步请求
$.ajax({
type:"post",
url:url,
data:params,
success:function(result){
alert(result);
//隐藏模态框
$('#myModal').modal('hide');
//重新执行查询,局部刷新
findActivitys();
}
});
}</code></pre><h3>启动tomcat服务器进行访问测试分析</h3><p>启动tomcat ,访问活动列表页面,如图所示:<br><img src="/img/bVbMrpN" alt="image.png" title="image.png"></p><p>在活动列表页面,点击添加按钮,然后启动活动添加模块框,执行添加操作如图所示:<br><img src="/img/bVbMrpR" alt="image.png" title="image.png"></p><p>Spring MVC 为方法参数赋值分析,如图所示:</p><p><img src="/img/bVbMAtA" alt="image.png" title="image.png"></p><p>日历控件拓展实现(开始时间和结束时间通过选择实现),效果展示,如图所示:<br><img src="/img/bVbMrpT" alt="image.png" title="image.png"></p><p>Bootstrap-datapicker控件的实现过程,如图所示:<br><img src="/img/bVbMxOc" alt="image.png" title="image.png"></p><h3>项目启动及运行过程中的BUG分析</h3><ul><li>BindingException,如图所示:</li></ul><p><img src="/img/bVbMxNs" alt="image.png" title="image.png"></p><ul><li>400 异常,如图所示:</li></ul><p><img src="/img/bVbMxNb" alt="image.png" title="image.png"></p><ul><li>服务端日期格式设计,如图所示:</li></ul><p><img src="/img/bVbMxNk" alt="image.png" title="image.png"></p><ul><li>完整性约束异常,如图所示:</li></ul><p><img src="/img/bVbMAtg" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本模块中讲解了活动模块中类的设计,映射设计,交互设计以及实现,然后可以基于此模块的练习加强对业务和技术上的设计及是实现。</p>
07-SpringBoot+MyBatis+Spring 技术整合实现商品模块的CRUD操作
https://segmentfault.com/a/1190000023837914
2020-08-31T20:09:53+08:00
2020-08-31T20:09:53+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
250
<h2>业务描述</h2><p>基于Spring,MyBatis,SpringBoot,Thymeleaf技术实现商品模块的增删改查操作。</p><h2>项目环境初始化</h2><h3>准备工作</h3><pre><code>1. MySQL(5.7)
2. JDK (1.8)
3. Maven (3.6.3)
4. STS(4.7.1)</code></pre><h3>数据库初始化</h3><p>打开mysql控制台,然后按如下步骤执行goods.sql文件。 <br>第一步:登录mysql。</p><pre><code>mysql –uroot –proot</code></pre><p>第二步:设置控制台编码方式。</p><pre><code>set names utf8;</code></pre><p>第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。</p><pre><code>source d:/goods.sql</code></pre><p>其中goods.sql文件内容如下:</p><pre><code>drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
id bigint primary key auto_increment,
name varchar(100) not null,
remark text,
createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,'java','very good',now());
insert into tb_goods values (null,'mysql','RDBMS',now());
insert into tb_goods values (null,'Oracle','RDBMS',now());
insert into tb_goods values (null,'java','very good',now());
insert into tb_goods values (null,'mysql','RDBMS',now());
insert into tb_goods values (null,'Oracle','RDBMS',now());
insert into tb_goods values (null,'java','very good',now());</code></pre><h3>创建项目并添加依赖</h3><h4>基于STS创建</h4><p>第一步:基于start.spring.io 创建项目并设置基本信息</p><p><img src="/img/bVbMbfY" alt="image.png" title="image.png"></p><p>第二步:创建项目时指定项目核心依赖</p><p><img src="/img/bVbMbgy" alt="image.png" title="image.png"></p><p>第三步:项目创建以后分析其结构</p><p><img src="/img/bVbMbgR" alt="image.png" title="image.png"></p><h4>基于IDEA创建</h4><p>第一步:基于start.spring.io 创建项目并设置基本信息</p><p><img src="/img/bVceWXJ" alt="image.png" title="image.png"></p><p><img src="/img/bVceWXT" alt="image.png" title="image.png"><br>第二步:创建项目module时指定项目核心依赖</p><p><img src="/img/bVceWX3" alt="image.png" title="image.png"></p><p>第三步:项目modul创建以后分析其结构</p><p><img src="/img/bVceWX5" alt="image.png" title="image.png"></p><p><img src="/img/bVceWX6" alt="image.png" title="image.png"></p><h3>项目配置文件内容初始化</h3><pre><code>#server
server.port=80
#server.servlet.context-path=/
#spring datasource
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#spring mybatis
mybatis.mapper-locations=classpath:/mapper/*/*.xml
#spring logging
logging.level.com.cy=debug
#spring thymeleaf
spring.thymeleaf.prefix=classpath:/templates/pages/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false</code></pre><h3>项目API架构设计</h3><p>其API架构设计,如图所示:</p><p><img src="/img/bVbMbt2" alt="image.png" title="image.png"></p><h2>商品查询业务实现</h2><h3>业务描述</h3><p>从商品库查询商品信息,并将商品信息呈现在页面上,如图所示:</p><p><img src="/img/bVbMbun" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>查询所有商品信息,其业务时序分析,如图所示:<br><img src="/img/bVbMbtQ" alt="image.png" title="image.png"></p><h3>Pojo类定义</h3><p>定义Goods对象,用于封装从数据库查询到的商品信息。</p><pre><code>package com.cy.pj.goods.pojo;
import java.util.Date;
public class Goods {
private Long id;//id bigint primary key auto_increment
private String name;//name varchar(100) not null
private String remark;//remark text
private Date createdTime;//createdTime datetime
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return "Goods [id=" + id + ", name=" + name + ",
remark=" + remark + ", createdTime=" + createdTime + "]";
}
}</code></pre><h3>Dao接口方法及映射定义</h3><p>在GoodsDao接口中定义商品查询方法以及SQL映射,基于此方法及SQL映射获取所有商品信息。代码如下:</p><pre><code>package com.cy.pj.goods.dao;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import com.cy.pj.goods.pojo.Goods;
@Mapper
public interface GoodsDao {
@Select("select * from tb_goods")
List<Goods> findGoods();
}
</code></pre><p>编写单元测试类进行测试分析:</p><pre><code>package com.cy.pj.goods.dao;
import com.cy.pj.goods.pojo.Goods;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class GoodsDaoTests {
@Autowired
private GoodsDao goodsDao;
@Test
void testFindGoods(){
List<Goods> goodsList=goodsDao.findGoods();
for(Goods g:goodsList){
System.out.println(g);
}
}
}</code></pre><p>测试结果问题分析</p><ul><li>数据库连不上,如图所示:</li></ul><p><img src="/img/bVbMblM" alt="image.png" title="image.png"></p><ul><li>连接数据库的url 配置问题,如图所示:</li></ul><p><img src="/img/bVceWYP" alt="image.png" title="image.png"></p><ul><li>Sql映射重复定义问题,如图所示:</li></ul><p><img src="/img/bVceWYO" alt="image.png" title="image.png"></p><ul><li>空指针问题,如图所示:</li></ul><p><img src="/img/bVceWYT" alt="image.png" title="image.png"></p><ul><li>SQL语法问题,如图所示:</li></ul><p><img src="/img/bVbMblW" alt="image.png" title="image.png"></p><h3>Service接口方法定义及实现</h3><p>GoodsService接口及商品查询方法定义</p><pre><code>package com.cy.pj.goods.service;
import java.util.List;
import com.cy.pj.goods.pojo.Goods;
public interface GoodsService {
List<Goods> findGoods();
}</code></pre><p>GoodsService接口实现类GoodsServiceImpl定义及方法实现</p><pre><code>package com.cy.pj.goods.service;
import java.util.List;
import com.cy.pj.goods.pojo.Goods;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
private GoodsDao goodsDao;
@Override
public List<Goods> findGoods(){
return goodsDao.findGoods();
}
}</code></pre><p>编写单元测试类进行测试分析</p><pre><code>package com.cy.pj.goods.service;
import com.cy.pj.goods.pojo.Goods;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class GoodsServiceTests {
@Autowired
private GoodsService goodsService;
@Test
void testFindGoods(){
List<Goods> goodsList=goodsService.findGoods();
//断言测试法(单元测试中常用的一种方式)
Assertions.assertEquals(true, goodsList.size()>0);
}
}</code></pre><p>测试结果问题分析</p><ul><li>依赖注入问题,如图所示:</li></ul><p><img src="/img/bVceWYU" alt="image.png" title="image.png"></p><h3>Controller对象方法定义及实现</h3><p>定义GoodsController类,并添加doGoodsUI方法,添加查询商品信息代码,并将查询到的商品信息存储到model,并返回goods页面。</p><pre><code>package com.cy.pj.goods.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import com.cy.pj.goods.pojo.Goods;
import com.cy.pj.goods.service.GoodsService;
@Controller //@Service,@Component
@RequestMapping("/goods/")
public class GoodsController {
//has a+di
@Autowired
private GoodsService goodsService;
@RequestMapping("doGoodsUI")
public String doGoodsUI(Model model) {
//调用业务层方法获取商品信息
List<Goods> list= goodsService.findGoods();
//将数据存储到请求作用域
model.addAttribute("list", list);
return "goods";//viewname
}
}</code></pre><h3>Goods商品列表页面设计及实现</h3><p>在templates/pages目录中添加goods.html页面,并在body中添加html元素,在运行内部使用thymeleaf标签属性获取数据,代码如下:</p><pre><code><table width="50%">
<thead>
<th>id</th>
<th>name</th>
<th>remark</th>
<th>createdTime</th>
<th>operation</th>
</thead>
<tbody>
<tr th:each="g:${list}">
<td th:text="${g.id}">1</td>
<td th:text="${g.name}">MySQL</td>
<td th:text="${g.remark}">DBMS</td>
<td th:text="${#dates.format(g.createdTime, 'yyyy/MM/dd HH:mm')}">2020/07/03</td>
<td><a>delete</a></td>
</tr>
</tbody>
</table></code></pre><blockquote>thymeleaf 是一种模板引擎,此引擎以html为模板,可以添加自定义标签属性,可以将服务端model中数据填充在页面上,然后实现与用于交互。其官网为thymeleaf.org</blockquote><p>Goods页面上数据呈现分析:</p><p><img src="/img/bVbMbk1" alt="image.png" title="image.png"></p><h3>启动Tomcat进行访问测试分析</h3><p>首先,启动tomcat,然后在打开浏览器,在地址栏输入访问地址,获取服务端数据并进行呈现,如图所示:<br><img src="/img/bVbMblp" alt="image.png" title="image.png"></p><h3>项目启动及运行过程中BUG及问题分析</h3><ul><li>STS控制台“?”符号,如图所示:</li></ul><p><img src="/img/bVbMblx" alt="image.png" title="image.png"></p><ul><li>服务启动失败,如图所示:</li></ul><p><img src="/img/bVbMblN" alt="image.png" title="image.png"></p><ul><li>模板不存在错误,如图所示:</li></ul><p><img src="/img/bVbMfDd" alt="image.png" title="image.png"></p><ul><li>日期格式不正确,如图所示:</li></ul><p><img src="/img/bVbMbl6" alt="image.png" title="image.png"></p><ul><li>页面上${}内容错误,如图所示:</li></ul><p><img src="/img/bVbMbmd" alt="image.png" title="image.png"></p><ul><li>页面日期格式不正确,如图所示:</li></ul><p><img src="/img/bVbMbmz" alt="image.png" title="image.png"></p><ul><li>依赖注入失败,如图所示:</li></ul><p><img src="/img/bVbMbmI" alt="image.png" title="image.png"></p><ul><li>空指针异常(NullPointerException),如图所示:</li></ul><p><img src="/img/bVbMbmY" alt="image.png" title="image.png"></p><h2>商品删除业务实现</h2><h3>业务描述</h3><p>从商品库查询商品信息后,点击页面上删除超链接,基于id删除当前行记录,如图所示:</p><p><img src="/img/bVbMbnZ" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>在商品呈现页面,用户执行删除操作,其删除时序如图所示:</p><p><img src="/img/bVbMbn0" alt="image.png" title="image.png"></p><h3>Dao接口方法及映射定义</h3><p>在GoodsDao接口中定义商品删除方法以及SQL映射,代码如下:</p><pre><code> @Delete("delete from tb_goods where id=#{id}")
int deleteById(Integer id);</code></pre><h3>Service接口方法定义及实现</h3><p>在GoodsService接口中添加删除方法,代码如下:</p><pre><code>int deleteById(Integer id);</code></pre><p>在GoodsService的实现类GoodsServiceImpl中添加deleteById方法实现。代码如下。</p><pre><code>@Override
public int deleteById(Integer id) {
long t1=System.currentTimeMillis();
int rows=goodsDao.deleteById(id);
long t2=System.currentTimeMillis();
System.out.println("execute time:"+(t2-t1));
return rows;
}</code></pre><h3>Controller对象方法定义及实现</h3><p>在GoodsController中的添加doDeleteById方法,代码如下:</p><pre><code> @RequestMapping("doDeleteById/{id}")
public String doDeleteById(@PathVariable Integer id){
goodsService.deleteById(id);
return "redirect:/goods/doGoodsUI";
}
</code></pre><p>说明:Restful 风格为一种软件架构编码风格,定义了一种url的格式,其url语法为/a/b/{c}/{d},在这样的语法结构中{}为一个变量表达式。假如我们希望在方法参数中获取rest url中变量表达式的值,可以使用@PathVariable注解对参数进行描述。</p><h3>Goods页面上删除超链接定义</h3><p>在goods.html页面中添加删除超链接,如图所示:</p><p><img src="/img/bVbMbpk" alt="image.png" title="image.png"></p><p>Thymeleaf 官方th:href应用说明,如图所示:</p><p><img src="/img/bVbMbps" alt="image.png" title="image.png"></p><p>删除操作中,客户端与服务端代码关联说明,如图所示:</p><p><img src="/img/bVbMbp4" alt="image.png" title="image.png"></p><h3>启动tomcat服务器进行访问测试分析</h3><p>首先,启动tomcat,然后在打开浏览器,在地址栏输入访问地址,获取服务端数据并进行呈现,接下来点击页面上的删除按钮,如图所示:</p><p><img src="/img/bVbMbp9" alt="image.png" title="image.png"></p><p>删除成功以后,的页面如图所示:</p><p><img src="/img/bVbMbqd" alt="image.png" title="image.png"></p><h3>项目启动及运行过程中的Bug及问题分析</h3><ul><li>SQL映射元素定义问题,如图所示:</li></ul><p><img src="/img/bVbMbqs" alt="image.png" title="image.png"></p><ul><li>客户端请求参数与服务端参数不匹配,如图所示:</li></ul><p><img src="/img/bVbMbqD" alt="image.png" title="image.png"></p><h2>商品添加业务实现</h2><h3>业务描述</h3><p>在Goods列表页面,添加添加按钮,进行添加页面,然后在添加页面输入商品相关信息,然后保存到数据库,如图所示:</p><p><img src="/img/bVbMbqZ" alt="image.png" title="image.png"></p><p>商品添加页面,设计如图所示:</p><p><img src="/img/bVbMbq1" alt="image.png" title="image.png"></p><h3>业务时序分析</h3><p>在商品添加页面,输入商品信息,然后提交到服务端进行保存,其时序分析如图所示:<br><img src="/img/bVbMbrn" alt="image.png" title="image.png"></p><h3>Dao接口方法及映射定义</h3><p>在GoodsDao中添加用于保存商品信息的接口方法以及SQL映射,代码如下:</p><pre><code>@Insert("insert into tb_goods(name,remark,createdTime)
values (#{name},#{remark},now())")
int insertObject(Goods entity);</code></pre><p>说明:当SQL语句比较复杂时,也可以将SQL定义到映射文件(xml文件)中。</p><h3>Service接口方法定义及实现</h3><p>在GoodsService接口中添加业务方法,用于实现商品信息添加,代码如下:</p><pre><code> int saveGoods(Goods entity);</code></pre><p>在GoodsSerivceImpl类中添加接口方法实现,代码如下:</p><pre><code> @Override
public int saveGoods(Goods entity) {
int rows=goodsDao.insertObject(entity);
return rows;
}
</code></pre><h3>Controller对象方法定义及实现</h3><p>在GoodsController类中添加用于处理商品添加请求的方法,代码如下:</p><pre><code>@RequestMapping("doSaveGoods")
public String doSaveGoods(Goods entity) {
goodsService.saveGoods(entity);
return "redirect:/goods/doGoodsUI";
}</code></pre><p>在GoodsController类中添加用于返回商品添加页面的方法,代码如下:</p><pre><code> @RequestMapping("doGoodsAddUI")
public String doGoodsAddUI() {
return "goods-add";
}
</code></pre><h3>Goods添加页面设计及实现</h3><p>在templates的pages目录中添加goods-add.html页面,代码如下</p><pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style type="text/css">
ul li {list-style-type: none;}
</style>
</head>
<body>
<h1>The Goods Add Page</h1>
<form th:action="@{/goods/doSaveGoods}" method="post">
<ul>
<li>name:
<li><input type="text" name="name">
<li>remark:
<li><textarea rows="5" cols="50" name="remark"></textarea>
<li><input type="submit" value="Save">
</ul>
</form>
</body>
</html></code></pre><p>在goods.html页面中添加,超链接可以跳转到添加页面,关键代码如下:</p><pre><code><a th:href="@{/goods/doGoodsAddUI}">添加商品</a></code></pre><h3>启动Tomcat服务器进行访问测试分析</h3><p>第一步:启动web服务器,检测启动过程是否OK,假如没有问题进入下一步。<br>第二步:打开浏览器在地址里输入<a href="https://link.segmentfault.com/?enc=Z%2BDxXl5pCadDXTwy6ig3rw%3D%3D.86QBhCB8eLC8XANh47B4z17y%2BOW1avKUO30JGhaZ%2FhOSotHqI0psTOTyPKXtab0MnucacvZSN7JvoXUTQTPhpDDw6y4pFSJq5qkYIiXwiIngBCut%2FFEIDf0RnSdEx56LrMxCC78UBdh%2BrN4nfZL%2FIW2%2BAZJU7XY1AGbTSZIvcM94bqn2MwSA5lmylnfSAe5YIoq5XQDVvDJ5mS%2Ff8XsUEdsXE6w6sGIp9mHAQqvWVxSskzZ9xQXetfKvFiv%2BIlUSKAmvDkllNNobCZh34Na3IOkwE2UxYifnFAs42q9FIdbcR5bJmqFA%2F1vXCm9jNQxY6RvXTXjdNPLmtIDvqN2lxw%3D%3D" rel="nofollow">http://localhost/goods/doGood...</a>,出现如下界面,如图所示:<br><img src="/img/bVbMbsk" alt="image.png" title="image.png"></p><p>第三步:在添加页面中填写表单,然后点击save按钮将表单数据提交到服务端,如图所示:<br><img src="/img/bVbMbsA" alt="image.png" title="image.png"><br>第四步:添加页面中表单数据提交过程分析,如图所示:<br><img src="/img/bVbMbsB" alt="image.png" title="image.png"></p><h3>项目启动及运行过程中的Bug及问题分析</h3><ul><li>客户端显示400异常,如图所示:</li></ul><p><img src="/img/bVbMbsQ" alt="image.png" title="image.png"></p><ul><li>保存时500异常,如图所示:</li></ul><p><img src="/img/bVbMbsT" alt="image.png" title="image.png"></p><ul><li>数据库完整性约束异常,如图所示:</li></ul><p><img src="/img/bVbMbs2" alt="image.png" title="image.png"></p><h2>商品修改业务实现</h2><h3>业务描述</h3><p>在商品列表页面,点击update选项,基于商品id查询当前行记录然后将其更新到goods-update页面,如图所示:<br><img src="/img/bVbMcI8" alt="image.png" title="image.png"><br>在update页面选中,修改商品信息,然后点击 update goods 将表单数据提交到服务端进行更新</p><h3>业务时序分析</h3><p>基于id查询商品信息的时序设计<br><img src="/img/bVbMcJd" alt="image.png" title="image.png"><br>将goods-update页面中的数据提交到服务端进行更新的时序设计<br><img src="/img/bVcIB1B" alt="image.png" title="image.png"></p><h3>Dao接口方法及映射定义</h3><p>在GoodsDao中添加基于id查询商品信息的方法及SQL映射,代码如下:</p><pre><code>@Select("select * from tb_goods where id=#{id}")
Goods findById(Integer id);</code></pre><p>在GoodsDao中添加基于id执行Goods商品更新的方法及SQL映射,代码如下:</p><pre><code> @Update("update tb_goods set name=#{name},remark=#{remark} where id=#{id}")
int updateGoods(Goods goods);</code></pre><h3>Service接口方法定义及实现</h3><p>在GoodsService 中添加基于id查询商品信息和更新商品信息的方法,代码如下:</p><pre><code>Goods findById(Integer id);
int updateGoods(Goods goods);</code></pre><p>在GoodsServiceImpl中基于id查询商品信息和更新商品信息的方法,代码如下:</p><pre><code> @Override
public Goods findById(Integer id) {
//.....
return goodsDao.findById(id);
}</code></pre><pre><code> @Override
public int updateGoods(Goods goods) {
return goodsDao.updateGoods(goods);
}</code></pre><h3>Controller对象方法定义及实现</h3><p>在GoodsController中添加基于id查询商品信息的方法,代码如下:</p><pre><code> @RequestMapping("doFindById/{id}")
public String doFindById(@PathVariable Integer id,Model model) {
Goods goods=goodsService.findById(id);
model.addAttribute("goods",goods);
return "goods-update";
}</code></pre><p>在GoodsController中添加更新商品信息的方法,代码如下:</p><pre><code> @RequestMapping("doUpdateGoods")
public String doUpdateGoods(Goods goods) {
goodsService.updateGoods(goods);
return "redirect:/goods/doGoodsUI";
}</code></pre><h3>Goods修改页面设计及实现</h3><p>在templates目录中添加goods-update.html页面,代码设计如下:</p><pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style type="text/css">
ul li {list-style-type: none}
</style>
</head>
<body>
<h1>The Goods Update Page</h1>
<form th:action="@{/goods/doUpdateGoods}" method="post">
<input type="hidden" name="id" th:value="${goods.id}">
<ul>
<li>name:
<li><input type="text" name="name" th:value="${goods.name}">
<li>remark:
<li><textarea rows="3" cols="30" name="remark" th:text="${goods.remark}"></textarea>
<li><input type="submit" value="Update Goods">
</ul>
</form>
</body>
</html></code></pre><h3>启动Tomcat服务进行访问测试分析</h3><p>启动tomcat服务,访问商品列表页面,如图所示:</p><p><img src="/img/bVbMcZV" alt="image.png" title="image.png"></p><p>在列表页面,点击update选项,进入更新页面</p><p><img src="/img/bVbMc0Q" alt="image.png" title="image.png"></p><p>在更新页面更新表单数据,然后提交,进入列表页面查看更新结果,如图所示:</p><p><img src="/img/bVbMc1A" alt="image.png" title="image.png"></p><h3>项目启动及运行过程中的BUG及问题分析</h3><ul><li>页面设计分析,如图所示:</li></ul><p><img src="/img/bVbMc2h" alt="image.png" title="image.png"></p><ul><li>页面源码分析,如图所示:</li></ul><p><img src="/img/bVbMc2q" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节重点讲解了SpringBoot工程下MyBatis,SpringMVC,Thymeleaf技术的综合应用,重点理解其业务实现过程以及问题的解决过程。</p>
04-SpringBoot工程下如何实现对HikariCP连接池的整合?
https://segmentfault.com/a/1190000023822917
2020-08-30T17:45:13+08:00
2020-08-30T17:45:13+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
184
<h2>池化思想分析</h2><p>池化思想是我们项目开发过程中的一种非常重要的思想,如整数池,字符串池,对象池、连接池、线程池等都是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。例如Integer对象的内部池应用,代码如下:</p><pre><code>package com.cy.java.pool;
public class TestInteger01 {
public static void main(String[] args) {
Integer n1=100;//Integer.valueOf(100) 编译时优化
Integer n2=100;
Integer n3=200;
Integer n4=200;//池中没有则new Integer(200)
System.out.println(n1==n2);//true
System.out.println(n3==n4);//false
}
}</code></pre><h2>数据库连接池简介</h2><h3>背景分析</h3><p>目开发过程中应用程序与数据库交互时,“获得连接”或“释放连接”是非常消耗系统资源的两个过程,频繁地进行数据库连接的建立和关闭会极大影响系统的性能,若多线程并发量很大,这样耗时的数据库连接就可能让系统变得卡顿。因为TCP连接的创建开支十分昂贵,并且数据库所能承载的TCP并发连接数也有限制,针对这种场景,数据库连接池应运而生。如下图所示:</p><p><img src="/img/bVbL61o" alt="image.png" title="image.png"></p><p>思考:假如现在是让你去设计一个连接池,你会从什么角度进行设计?<br>第一:物理存储结构(基于什么结构去存储数据)<br>第二:基于什么算法从池中取连接?<br>第三:基于什么算法从池中移除连接?<br>第四:当池中没有连接时,基于什么方式处理连接请求?<br>第五:池是可以共享,我们需要考虑池在访问的时并发安全?</p><h3>连接池原理分析</h3><p>在系统初始化的时候,在内存中开辟一片空间,将一定数量的数据库连接作为对象存储在对象池里,并对外提供数据库连接的获取和归还方法。用户访问数据库时,并不是建立一个新的连接,而是从数据库连接池中取出一个已有的空闲连接对象;使用完毕归还后的连接也不会马上关闭,而是由数据库连接池统一管理回收,为下一次借用做好准备。如果由于高并发请求导致数据库连接池中的连接被借用完毕,其他线程就会等待,直到有连接被归还。整个过程中,连接并不会关闭,而是源源不断地循环使用,有借有还。数据库连接池还可以通过设置其参数来控制连接池中的初始连接数、连接的上下限数,以及每个连接的最大使用次数、最大空闲时间等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。</p><h3>Java中的连接池</h3><p>Java官方,为了在应用程序中更好的应用连接池技术,定义了一套数据源规范,例如javax.sql.DataSource接口,基于这个接口,很多团队或个人创建了不同的连接池对象。然后我们的应用程序中通过耦合与DataSource接口,便可以方便的切换不同厂商的连接池。Java项目中通过连接池获取连接的一个基本过程,如下图所示:</p><p><img src="/img/bVbL61M" alt="image.png" title="image.png"></p><p>在上图中,用户通过DataSource对象的getConnection()方法,获取一个连接。假如池中有连接,则直接将连接返回给用户。假如池中没有连接,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect方法从数据库获取,拿到连接以后,可以将连接在池中放一份,然后将连接返回给调用方。连接需求方再次需要连接时,可以从池中获取,用完以后再还给池对象。</p><blockquote>数据库连接池在Java数据库相关中间件产品群中,应该算是底层最基础的一类产品,作为企业应用开发必不可少的组件,无数天才们为我们贡献了一个又一个的优秀产品,它们有的随时代发展,功成身退,有的则还在不断迭代,老而弥坚,更有新生代产品,或性能无敌,或功能全面。目前市场上常见的连接池有DBCP、C3P0,DRUID,HikariCP等。</blockquote><h2>SpringBoot工程下HikariCP整合测试</h2><h3>数据初始化</h3><p>打开mysql控制台,然后按如下步骤执行goods.sql文件。<br>第一步:登录mysql。</p><pre><code>mysql –uroot –proot</code></pre><p>第二步:设置控制台编码方式。</p><pre><code>set names utf8;</code></pre><p>第三步:执行goods.sql文件(切记不要打开文件复制到mysql客户端运行)。</p><pre><code>source d:/goods.sql</code></pre><p>其中goods.sql文件内容如下:</p><pre><code>drop database if exists dbgoods;
create database dbgoods default character set utf8;
use dbgoods;
create table tb_goods(
id bigint primary key auto_increment,
name varchar(100) not null,
remark text,
createdTime datetime not null
)engine=InnoDB;
insert into tb_goods values (null,'java','very good',now());
insert into tb_goods values (null,'mysql','RDBMS',now());
insert into tb_goods values (null,'Oracle','RDBMS',now());
insert into tb_goods values (null,'java','very good',now());
insert into tb_goods values (null,'mysql','RDBMS',now());
insert into tb_goods values (null,'Oracle','RDBMS',now());
insert into tb_goods values (null,'java','very good',now());</code></pre><h3>创建项目Module并添加相关依赖</h3><p>第一步:基于IDEA创建项目Module,如图所示:</p><p><img src="/img/bVbRG54" alt="image.png" title="image.png"></p><p>第二步:添加依赖<br>1) mysql数据库驱动依赖。</p><pre><code><dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency></code></pre><p>2) spring对象jdbc支持(此时会默认帮我们下载HiKariCP连接池)。</p><pre><code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency></code></pre><h3>配置HikariCP连接池</h3><p>打开application.properties配置文件,添加如下内容(必写)。</p><pre><code>spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root</code></pre><p>hikariCP 其它额外配置(可选),代码如下(具体配置不清晰的可自行百度):</p><pre><code>spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
</code></pre><h3>HikariCP 连接池测试</h3><p>单元测试API设计及应用分析,如图所示:</p><p><img src="/img/bVbRIA0" alt="image.png" title="image.png"></p><p>在项目中添加单元测试类及测试方法,代码如下:</p><pre><code>package com.cy.pj.common.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class DataSourceTests {
@Autowired
private DataSource dataSource;
@Test
public void testConnection() throws Exception{
System.out.println(dataSource.getConnection());
}
}</code></pre><p>在当前测试类中我们需要:</p><ul><li>掌握单元测试类、测试方法编写规范。</li><li>理解DataSource的设计规范及规范的实现。</li><li>分析在测试类中dataSource属性指向的对象是谁?</li><li>分析在测试类中DataSource的实现类对象由谁创建和管理?</li><li>思考基于DataSource接口获取连接的基本过程是怎样的?</li></ul><h3>测试BUG分析</h3><ul><li>数据库不存在,如图所示:</li></ul><p><img src="/img/bVbRIVy" alt="image.png" title="image.png"></p><ul><li>类编译错误,DataSource为javax.sql包中的类型,如图所示:</li></ul><p><img src="/img/bVbRIW7" alt="image.png" title="image.png"></p><ul><li>连接错误:数据库连接不上,如图所示:</li></ul><p><img src="/img/bVbRIWc" alt="image.png" title="image.png"></p><h2>基于HikariCP实现JDBC操作(练习)</h2><h3>业务分析</h3><p>基于HikariCP,借助JDBC技术访问商品库中的数据。</p><h3>API架构设计</h3><p>基于业务,进行API设计,如图所示:</p><p><img src="/img/bVbRIXP" alt="image.png" title="image.png"></p><h3>业务时序图分析</h3><p>基于业务需求,进行商品查询过程的的时序图设计,如图所示:</p><p><img src="/img/bVbRIYT" alt="image.png" title="image.png"></p><h3>业务代码设计及实现</h3><p>第一步:定义GoodsDao接口,例如:</p><pre><code>package com.cy.pj.goods.dao;
import java.util.List;
import java.util.Map;
/**
* 商品模块数据访问层接口
*/
public interface GoodsDao {
/**
* 查询所有商品信息,将每一行记录存储到一个map对象,然后将多个存储到list集合.
*/ List<Map<String,Object>> findGoods();
}</code></pre><p>第二步:创建GoodsDao接口实现类,代码如下:</p><pre><code>package com.cy.pj.goods.dao;
/**
* 此对象为一个商品数据层访问对象,现在要求在此类中定义一个方法,这个方法基于JDBC从从数据库获取商品信息,并将其封装到map集合,要求一个行记录一个map对象(key为表中字段名,值为字段名对应的值),多个map存储到list集合. @Repository此注解通常用于描述数据层实现类对象,本质上就是一个特殊的@Component, 都是要交给spring框架管理的一个Bean对象
*/
@Repository
public class DefaultGoodsDao implements GoodsDao{
@Autowired
private DataSource dataSource;//hikariCP
/**查询商品信息,一行记录映射为内存中的一个map对象*/
public List<Map<String,Object>> findGoods(){
Connection conn=null;//java.sql.*
Statement stmt=null;
ResultSet rs=null;
String sql="select * from tb_goods";
//1.获取连接(从连接池获取)
try {
conn=dataSource.getConnection();
//2.创建statement对象
stmt=conn.createStatement();
//3.发送sql
rs=stmt.executeQuery(sql);
//4.处理结果
List<Map<String,Object>> list=new ArrayList<>();
while(rs.next()){//循环一次取一行,一行记录映射为一个map对象
list.add( rowMap(rs));//将存储了一行记录的map对象再存储到list集合
}
return list;
}catch (SQLException e){
e.printStackTrace();
throw new RuntimeException(e);//转换为非检查异常(编译时不检测的异常)
}finally{
//5. 释放资源
close(rs,stmt,conn);
}
}</code></pre><p>定义行映射方法</p><pre><code> private Map<String,Object> rowMap(ResultSet rs)throws SQLException{
Map<String,Object> rowMap=new HashMap<>();
//方法1映射
//rowMap.put("id",rs.getInt("id"));
//rowMap.put("name",rs.getString("name"));
//rowMap.put("remark",rs.getString("remark"));
//rowMap.put("createdTime",rs.getTimestamp("createdTime"));
//方法2映射
ResultSetMetaData rsmd=rs.getMetaData();//获取元数据(包括表中的字段名)
int columnCount=rsmd.getColumnCount();//获取列的数量
for(int i=0;i<columnCount;i++){
rowMap.put(rsmd.getColumnLabel(i+1),rs.getObject(rsmd.getColumnLabel(i+1)));
//getColumnLabel(i+1);获取表中字段名或字段名对应的别名
}
return rowMap;
}</code></pre><p>定义释放资源的方法</p><pre><code> private void close(ResultSet rs,Statement stmt,Connection conn){
if(rs!=null)try{rs.close();}catch(Exception e){e.printStackTrace();}
if(stmt!=null)try{stmt.close();}catch(Exception e){e.printStackTrace();}
//这里的连接是返回到了池中
if(conn!=null)try{conn.close();}catch(Exception e){e.printStackTrace();}
}
}</code></pre><h3>测试代码的编写及运行</h3><p>定义单元测试类,并对其查询过程进行单元测试,例如:</p><pre><code>package com.cy.pj.goods.dao;
@SpringBootTest
public class GoodsDaoTests {
@Autowired
private GoodsDao goodsDao;
@Test
void testFindGoods(){
List<Map<String,Object>> list= goodsDao.findGoods();
for(Map<String,Object> map:list){
System.out.println(map);
}
}
}</code></pre><h3>测试运行过程中的BUG分析</h3><p>对测试过程中出现的问题进行记录,分析,总结.</p><h2>总结(Summary)</h2><p>总之,数据库连接池的为我们的项目开发及运行带来了很多优点,具体如下:</p><ul><li>资源重用更佳。</li></ul><p>由于数据库连接得到复用,减少了大量创建和关闭连接带来的开销,也大大减少了内存碎片和数据库临时进程、线程的数量,使得整体系统的运行更加平稳。</p><ul><li>系统调优更简便。</li></ul><p>使用了数据库连接池以后,由于资源重用,大大减少了频繁关闭连接的开销,大大降低了TIME_WAIT的出现频率。</p><ul><li>系统响应更快。</li></ul><p>数据库连接池在应用初始化的过程中一般都会提前准备好一些数据库连接,业务请求可以直接使用已经创建的连接,而不需要等待创建连接的开销。初始化数据库连接配合资源重用,使得数据库连接池可以大大缩短系统整体响应时间。</p><ul><li>连接管理更灵活。</li></ul><p>数据库连接池作为一款中间件,用户可以自行配置连接的最小数量、最大数量、最大空闲时间、获取连接超时间、心跳检测等。另外,用户也可以结合新的技术趋势,增加数据库连接池的动态配置、监控、故障演习等一系列实用的功能。</p>
02-如何基于STS实现SpringBoot 工程项目的创建?
https://segmentfault.com/a/1190000023418980
2020-07-29T13:53:25+08:00
2020-07-29T13:53:25+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
80
<h2>SpringBoot项目环境初始化</h2><p>本次项目的创建,需要如下软件已准备并已完成其配置.</p><ul><li>下载、安装、配置JDK(这里选择JDK8版本)。</li><li>下载、解压、配置maven(这里选择3.6.3版本)</li><li>下载、解压、配置STS(这里选择4.7.1版本)</li></ul><p>说明:对于环境下载,安装配置,自己通过官网或搜索引擎查询实现。</p><h2>SpringBoot项目创建过程分析及实现</h2><h3>基于STS内置start创建</h3><blockquote>第一步:打开sts软件,快捷键ctrl+n打开新建窗口,如图所示:</blockquote><p><img src="/img/bVbKqnx" alt="image.png" title="image.png"></p><blockquote>第二步:输入项目相关信息,如图所示:</blockquote><p><img src="/img/bVbKqny" alt="image.png" title="image.png"></p><blockquote>第三步:选择SpringBoot版本,如图所示:</blockquote><p><img src="/img/bVbKqnD" alt="image.png" title="image.png"></p><blockquote>第四步:项目结构展示,项目创建完以后的结构如下:</blockquote><p><img src="/img/bVbKqoI" alt="image.png" title="image.png"></p><h3>基于浏览器(Browser)创建</h3><blockquote>第一步:打开浏览器输入start.spring.io,然后输入项目信息,进行项目构建,如图所示:</blockquote><p><img src="/img/bVbKqqw" alt="image.png" title="image.png"></p><blockquote>第二步:将构建并下载下来的项目进行解压,如图所示:</blockquote><p><img src="/img/bVbKqqV" alt="image.png" title="image.png"></p><blockquote>第三步:将解压好的项目导入到sts中,如图所示:</blockquote><p><img src="/img/bVbKqrs" alt="image.png" title="image.png"></p><p><img src="/img/bVbKqss" alt="image.png" title="image.png"></p><blockquote>第四部步:项目结构展示,如图所示:</blockquote><p><img src="/img/bVbKqvc" alt="image.png" title="image.png"></p><h3>基于STS普通maven方式创建</h3><blockquote>第一步:创建普通maven项目,快捷键ctrl+n 搜maven,如图所示:</blockquote><p><img src="/img/bVbKqtI" alt="image.png" title="image.png"></p><blockquote>第二步:选择项目骨架,如图所示:</blockquote><p><img src="/img/bVbKqtP" alt="image.png" title="image.png"></p><blockquote>第三步:输入像项目信息,如图所示:</blockquote><p><img src="/img/bVbKqt9" alt="image.png" title="image.png"></p><blockquote>第四步:项目结构展示,如图所示:</blockquote><p><img src="/img/bVbKquv" alt="image.png" title="image.png"></p><blockquote>第五步:将已有项目的pom文件,项目启动类,配置文件拷贝到maven工程中,并进行适当修改,例如修改一下pom文件中项目名称.</blockquote><h2>SpringBoot项目创建过程问题分析</h2><h3>JDK安装配置问题</h3><blockquote>本地安装的JDK缺少资源,如图所示:</blockquote><p><img src="/img/bVbKr3J" alt="image.png" title="image.png"></p><h3>Maven配置及更新问题</h3><blockquote>Maven的配置文件错误,如图所示:</blockquote><p><img src="/img/bVbKr3T" alt="image.png" title="image.png"></p><blockquote>Maven中不能有多个私服镜像,如图所示</blockquote><p><img src="/img/bVbKr5d" alt="image.png" title="image.png"></p><blockquote>Maven项目依赖下载更新问题</blockquote><p><img src="/img/bVbKr5X" alt="image.png" title="image.png"></p><h3>网络连接(Connect)问题</h3><blockquote>spring 官方服务器连接超时.</blockquote><p><img src="/img/bVbKr4J" alt="image.png" title="image.png"></p><h2>总结(Summary)</h2><p>本小节重点讲解了基于STS工具创建和导入SpringBoot工程的几种方式以及在实践过程的一些问题的思考和解决方案。</p>
01-如何理解 Spring Boot技术?
https://segmentfault.com/a/1190000023410904
2020-07-28T20:03:29+08:00
2020-07-28T20:03:29+08:00
Jason
https://segmentfault.com/u/jason_5f0dbb9eaae42
136
<h2>Spring Boot 背景分析</h2><p>JAVAEE应用体系中繁重的配置、低下的开发效率、高难度的三方集成,复杂的部署流程等等一直被开发人员所诟病。即使是使用Spring这样的轻量级的资源整合框架,在实现其相对比较多的资源整合时,依旧需要大量的手动依赖管理,复杂的XML配置(还经常没有提示)。还有就是现在的软件生态应用也已经形成一定的规模,系统架构正在从单体架构,分布式架构,跨越到微服务架构。随着整个架构体系的变化,企业对技术的要求也在变化,现在的企业更注重技术的开箱即用,更注重技术在生态圈中的深度融合,更注重轻量级的运维。由此由此spring boot诞生。</p><h2>Spring Boot 要解决什么问题</h2><p>Spring Boot是由Pivotal团队提供的全新的Java软件开发框架(很多人现在把它理解为一个脚手架),其设计目的是用来简化Spring项目的初始搭建以及开发过程。该框架使用了特定的注解方式来进行配置,从而使开发人员不再需要大量的xml配置。不再需要大量的手动依赖管理。Spring Boot基于快速构建理念,通过约定大于配置,开箱即用的方式,希望能够在蓬勃发展的快速应用开发领域成为其领导者。</p><h2>Spring Boot 有哪些核心的关键特性</h2><blockquote><ul><li>起步依赖(Starter Dependency)。</li><li>自动配置(Auto Configuration)。</li><li>健康检查(Actator)-监控。</li><li>嵌入式服务(Tomcat,Jetty)。</li></ul></blockquote><h2>总结(Summary)</h2><p>总之,Spring Boot 框架就是要基于快速构建理念,基于约定大于配置方式,实现技术的开箱即用,以提高开发效率。</p>