首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

JSP EL表达式学习笔记

<%@page import="java.util.HashMap"%> <%@page import="java.util.Map"%> <%@page import="java.util.ArrayList"%> <%@page import="java.util.List"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="hah.*" errorPage="common/zz.jsp" %> <%-- errorPage="common/zz.jsp" --%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% String name1="name1"; int name2=111; int[] a=new int[3]; //int xx=10/0;//错误 //out.write(xx); a[1]=3; Student student=new Student("ss",22); request.setAttribute("name1", name1); session.setAttribute("name2", name2); request.setAttribute("intx", a); session.setAttribute("student",student); List<Student> list=new ArrayList<Student>(); list.add(new Student("11",111)); list.add(new Student("22",222)); list.add(new Student("33",333)); request.setAttribute("listx",list); Map<String,Student> map=new HashMap<String,Student>(); map.put("1", new Student("11",111)); map.put("2", new Student("22",222)); map.put("2", new Student("33",333)); pageContext.setAttribute("mapx",map); %> <%-- 注意 <%@ pageisELIgnored="true" %> 表示是否禁用EL语言,TRUE表示禁止.FALSE表示不禁止.JSP2.0中默认的启用EL语言。 --%> <%=pageContext.findAttribute("name2")%> <%-- 等效于${name2 } --%> <%-- ${name } 等价于 <%=pageContext.findAttribute("name")%> --%> EL表达式: ${name1 } <%-- 从指定的域中获取数据 否则它是按照小到大的顺序查找域对象的 pageScoep / requestScope / sessionScope / applicationScope --%> ${sessionScope.name2 } ${intx[1] } ${student.name } - ${student.age } ${student["name"] } -${student["age"] } <br/><!-- 这个比上面那个好 --> <%-- (点相对于调用getXX()方法) <%=((Student)pageContext.findAttribute("student")).getName()%> --%> ${listx[2]["name"] } - ${listx[2].age } <br/> <% for(int i=0;i<list.size();i++){ %> <%=list.get(i).getName() +" | "+list.get(i).getAge()%> <% } %> <br/> <!-- //里面不能放额外的字符串 --> ${listx[1]["name"] }---${listx[1].age } <%-- listx[0]等价于 (中括号相对于调用get(参数)方法) ((List)pageContext.findAttribute("list")).get(0) --%> <br/> ${mapx['1']["name"] } <br/> <%-- ${3>=2 } ${10!=3 } <br/> ${true||false} --%> <% String haha=""; request.setAttribute("haha", haha); %> 判断null:${haha==null } 判断空字符: ${haha=="" }<br/> 判空:${haha==null||haha=="" } 另一种判空: ${empty haha } </body> </html>

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

spring cloud(学习笔记) Enreka服务治理

服务治理是微服务架构最为核心和基础的模块,主要用来实现各个微服务实例的自动化注册和发现。 记录一下服务注册中心的搭建以及高可用注册中心的实现 1.首先创建两个基础 的spring boot工程,spring boot创建工程的网站:http://start.spring.io/,创建界面如下 2.解压工程,用Maven的形式导入工程(File->new->project from Existing Soures,选择解压工程导入) 3.两个工程中都添加依赖(eureka) 1 <properties> 2 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 3 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 4 <java.version>1.8</java.version> 5 <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> 6 </properties> 7 8 <dependencies> 9 <dependency> 10 <groupId>org.springframework.boot</groupId> 11 <artifactId>spring-boot-starter-web</artifactId> 12 </dependency> 13 <dependency> 14 <groupId>org.springframework.cloud</groupId> 15 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> 16 </dependency> 17 18 <dependency> 19 <groupId>org.springframework.boot</groupId> 20 <artifactId>spring-boot-starter-test</artifactId> 21 <scope>test</scope> 22 </dependency> 23 </dependencies> 24 25 <dependencyManagement> 26 <dependencies> 27 <dependency> 28 <groupId>org.springframework.cloud</groupId> 29 <artifactId>spring-cloud-dependencies</artifactId> 30 <version>${spring-cloud.version}</version> 31 <type>pom</type> 32 <scope>import</scope> 33 </dependency> 34 </dependencies> 35 </dependencyManagement> 36 37 <build> 38 <plugins> 39 <plugin> 40 <groupId>org.springframework.boot</groupId> 41 <artifactId>spring-boot-maven-plugin</artifactId> 42 </plugin> 43 </plugins> 44 </build> 4.在注册中心工程配置文件中添加服务 1 server.port=9000 2 3 spring.application.name=eureka-server 4 spring.profiles.active=dev 5 6 eureka.instance.hostname=localhost 7 eureka.client.register-with-eureka=false 8 eureka.client.fetch-registry=false 9 eureka.server.enable-self-preservation=false 不知道这些参数的自己百度 5.在注册中心的入口类中加入注解@EnableEurekaServer 1 package com.example.enrekaserver; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 7 @EnableEurekaServer 8 @SpringBootApplication 9 public class EnrekaServerApplication { 10 public static void main(String[] args) { 11 SpringApplication.run(EnrekaServerApplication.class, args); 12 } 13 } 6.启动注册中心,在浏览器中访问,界面如下 7.在另一个工程中,加入注解 @EnableDiscoveryClient 1 package com.example.demoOne; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 7 @EnableDiscoveryClient 8 @SpringBootApplication 9 public class DemoOneApplication { 10 public static void main(String[] args) { 11 SpringApplication.run(DemoOneApplication.class, args); 12 } 13 } 8.配置文件中加入配置,指定服务注册中心 1 spring.application.name=demoOne-service 2 spring.profiles.active=dev 3 eureka.client.service-url.defaultZone=http://localhost:9000/eureka/ 9.我们可以在需要注册是工程里添加一个类作为测试,并打印日志 1 package com.example.demoOne.didispace; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 5 import org.springframework.cloud.client.ServiceInstance; 6 import org.springframework.cloud.client.discovery.DiscoveryClient; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RestController; 10 11 import java.util.logging.Logger; 12 13 @RestController 14 public class HelloController { 15 private final Logger logger=Logger.getLogger(String.valueOf(getClass())); 16 17 @Autowired 18 private DiscoveryClient client; 19 20 @RequestMapping(value = "/hello",method = RequestMethod.POST) 21 public String index(){ 22 ServiceInstance instance = (ServiceInstance) client.getServices(); 23 logger.info("/hello,host:"+instance.getHost()+",service_id:"+instance.getServiceId()); 24 return "hello world"; 25 } 26 } 10.启动工程,我们可以在服务注册中心看到我们注册的服务 注册中心搭建成功。可以测试一下试试 小舟从此逝,江海寄余生。 --狐狸

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

Java学习(13)--包/修饰符

一、包 概述:其实就是文件夹,不允许包名重复,一般是域名反写 作用:对类进行分类管理 操作:增删改查 分类:1.按模块 2.按功能 二、修饰符 (1)分类: 权限修饰符: private(私有的;只能在内部访问), 默认(default;包访问权限) , protected(受保护的;子类访问权限),public(公共的;所有的都可以访问) 状态修饰符: static,final 抽象修饰符: abstract 权限修饰符 (2)常见的类及其组成的修饰 类:默认(default) ,public,final,abstract 常用的: public 成员变量:private, 默认(default) ,protected,public,static,final 常用的: private 构造方法:private, 默认(default) ,protected,public 常用的: public 成员方法:private, 默认(default) ,protected,public,static,final,abstract 常用的: public (3)另外比较常见的: public static final int X = 10; public static void show() {} public final void show() {} public abstract void show();

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

Java学习(14)--Object 类/String 类

一、Object 类 (1)Object是类层次结构的根类,所有的类都直接或者间接的继承自 Object类。 (2)Object类的构造方法有一个,并且是无参构造。这其实就是理解当时我们说过,子类构造方法默认访问父类的构造是无参构造 (3)要掌握的方法: A:toString() 返回对象的字符串表示,默认是由类的全路径 +'@'+哈希值的十六进制表示。这个表示其实是没有意义的,一般子类都会重写该方法。 B:equals() 比较两个对象是否相同。默认情况下,比较的是地址值是否相同。而比较地址值是没有意义的,所以,一般子类也会重写该方法。 (4) 要 了 解 的 方 法 : A:hashCode()返回对象的哈希值。不是实际地址值,可以理解为地址值。 B:getClass()返回对象的字节码文件对象,反射中我们会详细讲解 C:finalize() 用于垃圾回收,在不确定的时间 D:clone() 可以实现对象的克隆,包括成员变量的数据复制,但是它和两个引用指向同一个对象是有区别的。 (5)注意问题; A:直接输出一个对象名称,其实默认调用了该对象的 toString()方法。 B:面试题 ==和 equals() 的区别 ? A:== 基本类型:比较的是值是否相同 引用类型:比较的是地址值是否相同 B:equals() 只能比较引用类型。默认情况下,比较的是地址值是否相同。但是,我们可以根据自己的需要重写该方法。 = 表示地址相同(因为值相同),== 看地址,故ture ,equals 看值,故 ture new 则说明地址不同,== 看地址,故false ,equals 看值,故 ture 二、String 类 (1)概述:多个字符组成的一串数据。其实它可以和字符数组进行相互转换。 (2)构造方法: A:public String() B:public String(byte[] bytes) C:public String(byte[] bytes,int offset,int length) D:public String(char[] value) E:public String(char[] value,int offset,int count) F:public String(String original) 下面的这一个虽然不是构造方法,但是结果也是一个字符串对象 G:String s = "hello"; 输出:java (3)字符串的特点 A:字符串一旦被赋值,就不能改变。 注意:这里指的是字符串的内容不能改变,而不是引用不能改变。 B:字面值作为字符串对象和通过构造方法创建对象的不同 Strings = newString("hello");和String s= "hello"的区别? String s = new String(“hello”)会创建2(1)个对象,String s = “hello”创建1(0)个对象。注:当字符串常量池中有对象hello时括号内成立! (4)字符串的面试题 (看程序写结) A:==和 equals() String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2);// false System.out.println(s1.equals(s2));// true String s3 = new String("hello"); String s4 = "hello"; System.out.println(s3 == s4);// false System.out.println(s3.equals(s4));// true String s5 = "hello"; String s6 = "hello"; System.out.println(s5 == s6);// true System.out.println(s5.equals(s6));// true B:字符串的拼接 String s1 = "hello"; String s2 = "world"; String s3 = "helloworld"; System.out.println(s3 == s1 + s2);// false System.out.println(s3.equals((s1 + s2)));// true System.out.println(s3 == "hello" + "world"); // true System.out.println(s3.equals("hello" + "world"));// true (5)字符串的功能 A:判断功能 boolean equals(Object obj) 判断两个字符串是否相等(值相同即可) boolean equalsIgnoreCase(String str) (忽略大小写)判断两个字符串是否相等 boolean contains(String str)判断字符串中是否包含指定字符串 boolean startsWith(String str) 判断是否以指定字符串开头 boolean endsWith(String str)判断是否以指定字符串结尾 boolean isEmpty()判断字符串是否为空(字符串为 null 会报错) booleanmatches(String regex);//是否匹配正则表达式 B:获取功能 int length() 获取字符串长度 char charAt(int index) 获取索引处的字符 int indexOf(int char) 获取某指定字符在字符串中第一次出现的索引,没有找到则返回 -1 int indexOf(String str)获取某指定字符串在字符串中第一次出现的索引,没有找到则返回 -1 int indexOf(int ch,int fromIndex) 从指定索引处开始查找某个字符所在字符串的下标 int indexOf(String str,int fromIndex)从指定索引处开始查找某个字符串所在字符串的下标 String substring(int start) 从指定索引处截取字符串,获取字串 String substring(int start,int end) 左闭右开从指定索引处截取字符串,获取字串 C:转换功能 byte[] getBytes() 将字符串转换为字节数组 char[] toCharArray()将字符串转换为字符数组 static String valueOf(char[] chs) 将 char 类型数据转换为 String 数组 static String valueOf(int i)将 int 类型数据转换为 String 数组 输出:1002 String toLowerCase() 将字符串里面的大写转换成小写 String toUpperCase()将字符串里面的小写转换成大写 String concat(String str) 将指定字符拼接到字符串后面(效果等同于 + ) D:其他功能 a:替换功能 String replace(char old,char new) 字符替换 String replace(String old,String new)字符串替换 b: 去空格功能 String trim() 去除字符串两端空格(中间空格去不掉) c:按字典比较字符串大小 int compareTo(String str) 比较两个字符串 正数则调用方法字符串大,0则相等 int compareToIgnoreCase(String str)比较两个字符串(忽略大小写) 负数,故s 比 s1 要小 第一个数和第一个数比...第 n 个数和第 n 个数相比(相等则继续向下一位比较) 示例1: int[] arr ={1,2,3}; 输出结果:[1,2,3] 示例2: 字符串反转 键盘录入 “abc” 输出:“cba” 输出: 方法二 StringBuffer (1)用字符串做拼接,比较耗时并且也耗内存,而这种拼接操作又是比较常见的,为了解决这个问题, Java就提供了一个字符串缓冲区类:StringBuffer供我们使用。 (2)StringBuffer的构造方法 publicStringBuffer(): 无参构造方法 publicStringBuffer(intcapacity): 指定容量的字符串缓冲区对象 publicStringBuffer(Stringstr): 指定字符串内容的字符串缓冲区对象 (3)StringBuffer的常见功能(自己补齐方法的声明和方法的解释 ) A:添加功能 public StringBuffer append(String str): 可以把任意类型数据添加到字符串缓冲区 ,并返回字符串缓冲区本身 public StringBuffer insert(int offset,String str): 在指定位置把任意类型的数据插入到字符串缓冲区 ,并返回字符串缓冲区本身 B:删除功能 public StringBuffer deleteCharAt(int index): 删除指定位置的字符,并返回本身 public StringBuffer delete(int start,int end): 删除从指定位置开始指定位置结束的内容,并返回本身 C:替换功能 public StringBuffer replace(int start,int end,String str): 从 start 开始到 end 用 str 替换 D:反转功能 public StringBuffer reverse() E:截取功能(注意这个返回值) public Stringsubstring(int start) public Stringsubstring(int start,intend) (4)StringBuffer 的 练 习 ( 做 一 遍 ) A:String和 StringBuffer相互转换 String -- StringBuffer 构造方法 StringBuffer -- String toString() 方法 B:字符串的拼接 C:把字符串反转 D:判断一个字符串是否对称 (5)面试题 A:String,StringBuffer,StringBuilder 的区别 答:A:String是内容不可变的,而 StringBuffer, StringBuilder 都是内容可变的。 B:StringBuffer 是同步的,数据安全 ,效率低; StringBuilder是不同步的,数据不安全,效率高 B:StringBuffer 和数组的区别 答:二者都可以看出是一个容器,装其他的数据。 但是呢 ,StringBuffer 的数据最终是一个字符串数据。而数组可以放置多种数据,但必须是同一种数据类型的。 单线程操作字符串缓冲区下操作大量数据 = StringBuilder 多线程操作字符串缓冲区下操作大量数据 = StringBuffer (6)注意的问题: String 作为参数传递,效果和基本类型作为参数传递是一样的。

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

Mysql 常用(学习笔记二十二)

开启:./mysqld_safe --default-file=/etc/my.cnf --user=mysql 检查端口: netstat -tnlup|grep 3306 lsof -i:3306 为MySQL设置密码或者修改密码:mysqladmin -u root password “newpass” 如果root已经设置过密码,采用如下方法 mysqladmin -u root password oldpass “newpass” mysqladmin -u root password "xx" 登陆MySQL数据库:mysql -h -P -u -p xx show variables like '%character_set%' select version(); select user(); create database oldboy character set gbk; grant all on *.* to oldboy@'%' identified by '12356'; flush privileges; show grants for oldboy@'localhost'; select user,host from mysql.user; use oldboy select database(); create table test(id int(4) not nullname varchar(64) not null); desc test show columns from test show fule columns from test; insert into test value(xx,'xx') insert into test(id,name) values(2,'老男孩'),(3,'oldboyedu'); select * from test where name='oldboy'; update test set name='oldgirl' where id=1; alter table test add age tinyin(2) after id; mysqldump -uroot -pxxx -B oldboy>xx.sql truncate test drop table test; source /opt/oldboy1.sql alter table test add shouji char(11) after name; insert into test(id,name,shouji) values(1,'aige','13555555'),(2,'oldboy','1388888888'); drop index xxx on xxx select * from test where shouji like '135%' and name like 'oldboy'; revoke select on oldbody.* from oldboy@'%' mysql -uroot -poldboy123 -e "show grants for root@'localhost'" | grep -i select select user,host from mysql.user; drop user oldboy@'localhost'; mysqladmin -uroot -poldboy123 shutdown 使用mysqldump以特定编码导出数据(其中utf8为所需编码,可按需修改),如: mysqldump --default-character-set=utf8 -t -u root -p 数据库名 >/root/data.sql default_character_set=utf8 alter database test default character set utf8; alter table category default character set utf8; 请解释关系型数据库概念及主要特点? 关系型数据库 1、概念 所谓的关系型数据库指的是:采用了关系模型来组织数据的数据库。简单讲,关系模型就是二维表格模型。二维表格中的行在数据库中我们称之为记录,列在数据库中我们成为字段。 2、常见的数据库 关系数据库我们接触的比较多些,经常见到的有access、sqlserver、mysql和orcal、DB2等。 3、关系型数据库的优点 能够保持数据的一致性 4、关系型数据库的不足 大量数据的操作 字段的不固定 对表的索引以及表机构的更新 关系型数据库模型是把复杂的数据结构归结为简单的二元关系,对数据的操作都是建立一个或多个关系表格上,最大的特点就是二维的表格,通过SQL结构查询语句存取数据,保持数据一致性方面很强大 非关系型数据库也被称为NoSQL数据库,数据存储不需有特有固定的表结构 特点:高性能、高并发、简单易安装 1、memcaced 纯内存 2、redis 持久化缓存 3、mongodb 面向文档 如果需要短时间响应的查询操作,没有良好模式定义的数据存储,或者模式更改频繁的数据存储还是用NoSQL sql语句分类如下 DDL 数据定义语言,用来定义数据库对象:库、表、列 代表性关键字:create alter drop DML 数据操作语言,用来定义数据库记录 代表性关键字:insert delete update DCL 数据控制语言,用来定义访问权限和安全级别 代表性关键字:grant deny revoke DQL 数据查询语言,用来查询记录数据 代表性关键字:select DDL DML DCL DQL char长度是固定不可变的,varchar长度是可变的(在设定内)比如同样写入cn字符,char类型对应的长度是4(cn+两个空格),但varchar类型对应长度是2 create database mingongge default charset utf8 collate utf8_general_ci; grant all on *.* to mingongge@'172.16.1.0/24' identified by '123456'; mysql多实例就是在同一台服务器上启用多个mysql服务,它们监听不同的端口,运行多个服务进程,它们相互独立,互不影响的对外提供服务,便于节约服务器资源与后期架构扩展 多实例的配置方法有两种: 1、一个实例一个配置文件,不同端口 2、同一配置文件(my.cnf)下配置不同实例,基于mysqld_multi工具 如何加强MySQL安全,请给出可行的具体措施? 1、删除数据库不使用的默认用户 2、配置相应的权限(包括远程连接) 3、不可在命令行界面下输入数据库的密码 4、定期修改密码与加强密码的复杂度 delete和truncate删除数据的区别? 前者删除数据可以恢复,它是逐条删除速度慢 后者是物理删除,不可恢复,它是整体删除速度快 sort_buffer_size参数作用?如何在线修改生效? 在每个connection(session)第一次连接时需要使用到,来提访问性能 set global sort_buffer_size = 2M 如何在线正确清理MySQL binlog? 首先查看主从库正在使用的binlog文件名称 show master(slave) status\G 删除之前一定要备份 purge master logs before'2017-09-01 00:00:00'; purge master logs before '' :Binlog工作模式有哪些?各什么特点,企业如何选择? 1.Row(行模式); 日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改 2.Statement(语句模式) 每一条修改的数据都会完整的记录到主库master的binlog里面,在slave上完整执行在master执行的sql语句 3.mixed(混合模式) 结合前面的两种模式,如果在工作中有使用函数 或者触发器等特殊功能需求的时候,使用混合模式 数据量达到比较高时候,它就会选择 statement模式,而不会选择Row Level行模式 误操作执行了一个drop库SQL语句,如何完整恢复? 将0点时的binlog文件与全备到故障期间的binlog文件合并导出成sql语句 mysqlbinlog --no-defaults mysql-bin.000011 mysql-bin.000012 >bin.sql 将导出的sql语句中drop语句删除,恢复到数据库中 mysql -uroot -pmysql123 < bin.sql mysqldump备份使用了-A -B参数,如何实现恢复单表? -A 此参数作用是备份所有数据库(相当于--all-databases) -B databasename 备份指定数据(单库备份使用) 详述MySQL主从复制原理及配置主从的完整步骤 主从复制的原理如下:主库开启binlog功能并授权从库连接主库,从库通过change master得到主库的相关同步信息,然后连接主库进行验证,主库IO线程根据从库slave线程的请求,从master.info开始记录的位置点向下开始取信息, 同时把取到的位置点和最新的位置与binlog信息一同发给从库IO线程,从库将相关的sql语句存放在relay-log里面,最终从库的sql线程将relay-log里的sql语句应用到从库上,至此整个同步过程完成,之后将是无限重复上述过程完整步骤如下:1、主库开启binlog功能,并进行全备,将全备文件推送到从库服务器上2、show master status\G 记录下当前的位置信息及二进制文件名3、登陆从库恢复全备文件4、执行change master to 语句5、执行start slave and show slave status\G 如何开启从库的binlog功能? 修改配置文件加上下面的配置 log_bin=slave-bin log_bin_index=slave-bin.index 需要重启服务生效 MySQL如何实现双向互为主从复制,并说明应用场景? auto_increment_increment=2 auto_increment_offset=1 log-slave-updates [mysqld] auto_increment_increment = 2 #起始ID auto_increment_offset = 1 #ID自增间隔 log-slave-updates 从库配置 [mysqld] auto_increment_increment = 2 #起始ID auto_increment_offset = 2 #ID自增间隔 log-slave-updates 主从库服务器都需要重启mysql服务 MySQL如何实现级联同步,并说明应用场景? 级联同步主要应用在从库需要做为其它数据库的主库 在需要做级联同步的数据库配置文件增加下面的配置即可 log_bin=slave-bin log_bin_index=slave-bin.index 如何监控主从复制是否故障? mysql -uroot -ppassowrd -e "show slave status\G" |grep -E "Slave_IO_Running|Slave_SQL_Running"|awk '{print $2}'|grep -c Yes 通过判断Yes的个数来监控主从复制状态,正常情况等于2 mysql -uroot -pxxx -e "show slave status\G"|grep -E "Slave_IO_Running|Slave_SQL_Running"|awk '{print $2}'|grep -c Yes MySQL数据库如何实现读写分离? atlas 生产一主多从从库宕机,如何手工恢复? 1、执行stop slave 或者停止服务 2、修复好从库数据库 3、然后重新操作主库同步 生产一主多从主库宕机,如何手工恢复? 1、登陆各个从库停止同步,并查看谁的数据最新,将它设置为新主库让其它从库同步其数据2、修复好主库之后,生新操作主从同步的步骤就可以了 #需要注意的新的主库如果之前是只读,需要关闭此功能让其可写#需要在新从库创建与之前主库相同的同步的用户与权限#其它从库执行change master to master_port=新主库的端口,start slave MySQL出现复制延迟有哪些原因?如何解决? 1、需要同步的从库数据太多 2、从库的硬件资源较差,需要提升 3、网络问题,需要提升网络带宽 4、主库的数据写入量较大,需要优配置和硬件资源 5、sql语句执行过长导致,需要优化 给出企业生产大型MySQL集群架构可行备份方案? 1、双主多从,主从同步的架构,然后实行某个从库专业做为备份服务器 2、编写脚本实行分库分表进行备份,并加入定时任务 3、最终将备份服务推送至内网专业服务器,数据库服务器本地保留一周 4、备份服务器根据实际情况来保留备份数据(一般30天) 什么是数据库事务,事务有哪些特性?企业如何选择? 数据库事务是指逻辑上的一组sql语句,组成这组操作的各个语句,执行时要么成功,要么失败 特点:具有原子性、隔离性、持久性、一致性 请解释全备、增备、冷备、热备概念及企业实践经验? 全备:数据库所有数据的一次完整备份,也就是备份当前数据库的所有数据 增备:就在上次备份的基础上备份到现在所有新增的数据 冷备:停止服务的基础上进行备份操作 热备:实行在线进行备份操作,不影响数据库的正常运行 全备在企业中基本上是每周或天一次,其它时间是进行增量备份 热备使用的情况是有两台数据库在同时提供服务的情况,针对归档模式的数据库 冷备使用情况有企业初期,数据量不大且服务器数量不多,可能会执行某些库、表结构等重大操作时 MySQL的SQL语句如何优化? 建立主键与增加索引 企业生产MySQL集群架构如何设计备份方案? 1、集群架构可采用双主多从的模式,但实际双主只有一主在线提供服务,两台主之间做互备 2、另外的从可做读的负载均衡,然后将其中一台抽出专业做备份 企业生产MySQL如何优化(请多角度描述)? 1、提升服务器硬件资源与网络带宽 2、优化mysql服务配置文件 3、开启慢查询日志然后分析问题所在 MySQL高可用方案有哪些,各自特点,企业如何选择? 高可用方案有 1、主从架构 2、MySQL+MMM 3、MySQL+MHA 4、mysql+haproxy+drbd 5、mysql+proxy+amoeba 如何批量更改数据库表的引擎? 通过mysqldump命令备份出一个sql文件,再使用sed命令替换 或者执行下面的脚本进行修改 #!/bin/sh user=root passwd=123456 cmd="mysql -u$user -p$passwd " dump="mysqldump -u$user -p$passwd" for database in `$cmd -e "show databases;"|sed '1,2d'|egrep -v "mysql|performance_schema"` do for tables in `dump -e "show tables from $databses;"|sed '1d'` do $cmd "alter table $database.$tables engine = MyISAm;" done done 如何批量更改数据库字符集? 通过mysqldump命令备份出一个sql文件,再使用sed命令替换sed -i 's/GBK/UTF8/g' 网站打开慢,请给出排查方法,如是数据库慢导致,如何排查并解决,请分析并举例? 1、可以使用top free 等命令分析系统性能等方面的问题 2、如是因为数据库的原因造成的,就需要查看慢查询日志去查找并分析问题所在

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

Nginx upload上传模块(学习笔记十七)

上传模块配置样例: # 上传大小限制(包括所有内容) client_max_body_size 100m; # 上传path配置 location /upload { # 转到后台处理URL upload_pass /uploadHandle; # 临时保存路径 # 可以使用散列 upload_store /tmp/nginx_upload; # 上传文件的权限,rw表示读写 r只读 upload_store_access user:rw; # 这里写入http报头,pass到后台页面后能获取这里set的报头字段 upload_set_form_field "${upload_field_name}_name"$upload_file_name; upload_set_form_field "${upload_field_name}_content_type"$upload_content_type; upload_set_form_field "${upload_field_name}_path"$upload_tmp_path; # Upload模块自动生成的一些信息,如文件大小与文件md5值 upload_aggregate_form_field "${upload_field_name}_md5"$upload_file_md5; upload_aggregate_form_field "${upload_field_name}_size"$upload_file_size; # 允许的字段,允许全部可以 "^.*$" upload_pass_form_field "^submit$|^description$"; # 每秒字节速度控制,0表示不受控制,默认0 upload_limit_rate 0; # 如果pass页面是以下状态码,就删除此次上传的临时文件 upload_cleanup 400 404 499 500-505; } 一. nginx upload module原理 官方文档:http://www.grid.net.ru/nginx/upload.en.html Nginx upload module通过nginx服务来接受用户上传的文件,自动解析请求体中存储的所有文件上传到upload_store指定的目录下。这些文件信息从原始请求体中分离并根据nginx.conf中的配置重新组装好上传参数,交由upload_pass指定的段处理,从而允许处理任意上传文件。每个上传文件中的file字段值被一系列的upload_set_form_field指令值替换。每个上传文件的内容可以从$upload_tmp_path变量读取,或者可以将文件转移到目的目录下。上传的文件移除可以通过upload_cleanup指令控制。如果请求的方法不是POST,模块将返回405错误(405 Not Allowed),该错误提示可以通过error_page指令处理。 具体的过程如下: 1. 用户访问能够选择上传文件的页面 2. 用户提交表单 3. 浏览器把文件和有关文件的信息作为请求的一部分发送给服务器 4. 服务器把文件保存到临时存储目录下upload_store 5. upload_pass指定的处理表单提交的php页面将文件从upload_store拷贝到持久存储位置 二.nginx upload module配置参数 upload_pass 指明后续处理的php地址。文件中的字段将被分离和取代,包含必要的信息处理上传文件。 upload_resumable 是否启动可恢复上传。 upload_store 指定上传文件存放地址(目录)。目录可以散列,在这种情况下,在nginx启动前,所有的子目录必须存在。 upload_state_store 指定保存上传文件可恢复上传的文件状态信息目录。目录可以散列,在这种情况下,在nginx启动前,所有的子目录必须存在。 upload_store_access 上传文件的访问权限,user:r是指用户可读 upload_pass_form_field 从表单原样转到后端的参数,可以正则表达式表示。: $upload_field_name — 原始文件中的字段的名称 upload_pass_form_field “^submit$|^description$”; 意思是把submit,description这两个字段也原样通过upload_pass传递到后端php处理。如果希望把所有的表单字段都传给后端可以用upload_pass_form_field “^.*$”; upload_set_form_field 名称和值都可能包含以下特殊变量: $upload_field_name 表单的name值 $upload_content_type 上传文件的类型 $upload_file_name 客户端上传的原始文件名称 $upload_tmp_path 文件上传后保存在服务端的位置 upload_aggregate_form_field 可以多使用的几个变量,文件接收完毕后生成的并传递到后端 $upload_file_md5 文件的MD5校验值 $upload_file_md5_uc 大写字母表示的MD5校验值 $upload_file_sha1 文件的SHA1校验值 $upload_file_sha1_uc 大写字母表示的SHA1校验值 $upload_file_crc32 16进制表示的文件CRC32值 $upload_file_size 文件大小 $upload_file_number 请求体中的文件序号 这些字段值是在文件成功上传后计算的。 upload_cleanup 如果出现400 404 499 500-505之类的错误,则删除上传的文件 upload_buffer_size 上传缓冲区大小 upload_max_part_header_len 指定头部分最大长度字节。 upload_max_file_size 指定上传文件最大大小,软限制。client_max_body_size硬限制。 upload_limit_rate 上传限速,如果设置为0则表示不限制。 upload_max_output_body_len 超过这个大小,将报403错(Request entity too large)。 upload_tame_arrays 指定文件字段名的方括号是否删除 upload_pass_args 是否转发参数。 三. nginx配置 # wget http://www.nginx.org/download/nginx-1.2.2.tar.gz # wget http://www.grid.net.ru/nginx/download/nginx_upload_module-2.2.0.tar.gz # tar zxvf nginx_upload_module-2.2.0.tar.gz -c ../software/ # tar zxvf nginx_upload_module-2.2.0.tar.gz -C ../software/ # ./configure –prefix=/usr/local/nginx –add-module=../nginx_upload_module-2.2.0 –with-http_secure_link_module # make # make install # vi nginx.conf user www-data; worker_processes 20; error_log logs/error.log notice; working_directory /usr/local/nginx; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; root /www/web/upload; server { listen 80; server_name 192.168.41.129; error_page 405 =200 @405; //处理405错误 location / { index index.html index.htm index.php; } location @405 { root /www/web/upload; } location ~ \.php$ { try_files $uri /404.html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include /etc/nginx/fastcgi_params; } client_max_body_size 100m; # 上传页面提交到这个location location /upload { # 文件上传以后转交给后端的php代码处理 upload_pass @test; # 上传文件的临时存储位置,目录是散列的,应该存在子目录0 1 2 3 4 5 6 7 8 9 upload_store /www/web/upload/tmp 1; upload_store_access user:r; # 设置请求体的字段 upload_set_form_field “${upload_field_name}_name” $upload_file_name; upload_set_form_field “${upload_field_name}_content_type” $upload_content_type; upload_set_form_field “${upload_field_name}_path” $upload_tmp_path; # 指示后端关于上传文件的md5值和文件大小 upload_aggregate_form_field “${upload_field_name}_md5″ $upload_file_md5; upload_aggregate_form_field “${upload_field_name}_size” $upload_file_size; # 指示原样转到后端的参数,可以用正则表达式表示 upload_pass_form_field “^submit$|^description$”; upload_pass_args on; } # 将请求转到后端的地址处理 location @test { rewrite ^(.*)$ /test.php last; } } } 四. 上传界面 # cat /www/web/upload/upload.html Test upload Select files to upload 五. upload_pass处理内容 # cat test.php //这里只是简单的打印出来,便于先理解上传原理。请对着输出内容理解下nginx upload module配置参数。 print_r($_POST); ?> 对上传文件的处理请参考:http://cn.php.net/manual/en/features.file-upload.php 六. 测试 http://192.168.41.129/upload.html 输出内容如下所示: Array ( [file1_name] => LearningPerl, Sixth Edition.pdf [file1_content_type] => application/pdf [file1_path] => /www/web/upload/tmp/4/0000000014 [file1_md5] => 87032cc58109f5c6bb866d2684f9b48c [file1_size] => 8927511 [file2_name] => Programming Perl, 4th Edition.pdf [file2_content_type] => application/pdf [file2_path] => /www/web/upload/tmp/5/0000000015 [file2_md5] => 82a52df177a8912c06af276581cfd5e4 [file2_size] => 21146356 [submit] => Upload ) 注意:需要修改php.ini以下参数 file_uploads on 是否允许通过http上传 upload_max_filesize 8m 允许上传文件的最大大小 post_max_size 8m 通过表单POST给php所能接收的最大值 另外nginx.conf中设置上传文件大小 upload_max_file_size 软限制 client_max_body_size 硬限制 一.nginxuploadmodule原理 官方文档:http://www.grid.net.ru/nginx/upload.en.html Nginxuploadmodule通过nginx服务来接受用户上传的文件,自动解析请求体中存储的所有文件上传到upload_store指定的目录下。这些文件信息从原始请求体中分离并根据nginx.conf中的配置重新组装好上传参数,交由upload_pass指定的段处理,从而允许处理任意上传文件。每个上传文件中的file字段值被一系列的upload_set_form_field指令值替换。每个上传文件的内容可以从$upload_tmp_path变量读取,或者可以将文件转移到目的目录下。上传的文件移除可以通过upload_cleanup指令控制。如果请求的方法不是POST,模块将返回405错误(405NotAllowed),该错误提示可以通过error_page指令处理。 具体的过程如下: 1.用户访问能够选择上传文件的页面 2.用户提交表单 3.浏览器把文件和有关文件的信息作为请求的一部分发送给服务器 4.服务器把文件保存到临时存储目录下upload_store 5.upload_pass指定的处理表单提交的php页面将文件从upload_store拷贝到持久存储位置 P.S. 安装编译方法 1.下载 1wget http://www.grid.net.ru/nginx/download/nginx_upload_module-2.2.0.tar.gz 2.编译(在NGINX编译目录执行以下命令,其中--add-module=你下载解压的上传插件目录) ./configure--user=www --group=www --prefix=/usr/local/nginx--with-http_stub_status_module --with-http_s sl_module --with-http_gzip_static_module --add-module=/data/downfile/nginx_upload_module-2.2.0 二.nginxuploadmodule配置参数 upload_pass指明后续处理的php地址。文件中的字段将被分离和取代,包含必要的信息处理上传文件。 upload_resumable是否启动可恢复上传。 upload_store指定上传文件存放地址(目录)。目录可以散列,在这种情况下,在nginx启动前,所有的子目录必须存在。 upload_state_store指定保存上传文件可恢复上传的文件状态信息目录。目录可以散列,在这种情况下,在nginx启动前,所有的子目录必须存在。 upload_store_access上传文件的访问权限,user:r是指用户可读 upload_pass_form_field从表单原样转到后端的参数,可以正则表达式表示。: $upload_field_name—原始文件中的字段的名称 upload_pass_form_field“^submit$|^description$”; 意思是把submit,description这两个字段也原样通过upload_pass传递到后端php处理。如果希望把所有的表单字段都传给后端可以用upload_pass_form_field“^.*$”; upload_set_form_field名称和值都可能包含以下特殊变量: $upload_field_name表单的name值 $upload_content_type上传文件的类型 $upload_file_name客户端上传的原始文件名称 $upload_tmp_path文件上传后保存在服务端的位置 upload_aggregate_form_field可以多使用的几个变量,文件接收完毕后生成的并传递到后端 $upload_file_md5文件的MD5校验值 $upload_file_md5_uc大写字母表示的MD5校验值 $upload_file_sha1文件的SHA1校验值 $upload_file_sha1_uc大写字母表示的SHA1校验值 $upload_file_crc3216进制表示的文件CRC32值 $upload_file_size文件大小 $upload_file_number请求体中的文件序号 这些字段值是在文件成功上传后计算的。 upload_cleanup如果出现400404499500-505之类的错误,则删除上传的文件 upload_buffer_size上传缓冲区大小 upload_max_part_header_len指定头部分最大长度字节。 upload_max_file_size指定上传文件最大大小,软限制。client_max_body_size硬限制。 upload_limit_rate上传限速,如果设置为0则表示不限制。 upload_max_output_body_len超过这个大小,将报403错(Requestentitytoolarge)。 upload_tame_arrays指定文件字段名的方括号是否删除 upload_pass_args是否转发参数。 三.nginx配置 #wget http: //www.nginx.org/download/nginx-1.2.2.tar.gz #wget http: //www.grid.net.ru/nginx/download/nginx_upload_module-2.2.0.tar.gz #tar zxvf nginx_upload_module - 2.2.0.tar.gz - c.. / software / #tar zxvf nginx_upload_module - 2.2.0.tar.gz - C.. / software / #. / configure–prefix = /usr/local / nginx–add - module = .. / nginx_upload_module - 2.2.0–with - http_secure_link_module #make #make install #vi nginx.conf user www - data; worker_processes 20; error_log logs / error.log notice; working_directory / usr /local/ nginx; events { worker_connections 1024; } http { include mime.types; default_type application / octet - stream; root / www / web / upload; server { listen 80; server_name 192.168.41.129; error_page 405 = 200@405;//处理405错误 location / { index index.html index.htm index.php; } location@405 { root / www / web / upload; } location~\.php$ { try_files $uri / 404.html; fastcgi_pass 127.0.0.1 : 9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include/etc/nginx/fastcgi_params; } client_max_body_size 100m;#上传页面提交到这个location location / upload { #文件上传以后转交给后端的php代码处理 upload_pass@test; #上传文件的临时存储位置,目录是散列的,应该存在子目录0 1 2 3 4 5 6 7 8 9 upload_store/www/web/upload/tmp1; upload_store_access user: r; #设置请求体的字段 upload_set_form_field "${upload_field_name}_name" $upload_file_name; upload_set_form_field"${upload_field_name}_content_type"$upload_content_type; upload_set_form_field"${upload_field_name}_path"$upload_tmp_path; # 指示后端关于上传文件的md5值和文件大小 upload_aggregate_form_field"${upload_field_name}_md5"$upload_file_md5; upload_aggregate_form_field"${upload_field_name}_size"$upload_file_size; # 指示原样转到后端的参数,可以用正则表达式表示 upload_pass_form_field"^submit$|^description$"; upload_pass_args on; }#将请求转到后端的地址处理 location@test{ rewrite ^ (. * ) $ /test.php last; } } } 四.上传界面 #cat/www/web/upload/upload.html 五.upload_pass处理内容 #cattest.php//这里只是简单的打印出来,便于先理解上传原理。请对着输出内容理解下nginxuploadmodule配置参数。 print_r($_POST); ?> 对上传文件的处理请参考:http://cn.php.net/manual/en/features.file-upload.php 六.测试 http://192.168.41.129/upload.html 输出内容如下所示: Array ( [file1_name]=>LearningPerl,SixthEdition.pdf [file1_content_type]=>application/pdf [file1_path]=>/www/web/upload/tmp/4/0000000014 [file1_md5]=>87032cc58109f5c6bb866d2684f9b48c [file1_size]=>8927511 [file2_name]=>ProgrammingPerl,4thEdition.pdf [file2_content_type]=>application/pdf [file2_path]=>/www/web/upload/tmp/5/0000000015 [file2_md5]=>82a52df177a8912c06af276581cfd5e4 [file2_size]=>21146356 [submit]=>Upload ) 注意:需要修改php.ini以下参数 file_uploadson是否允许通过http上传 upload_max_filesize8m允许上传文件的最大大小 post_max_size8m通过表单POST给php所能接收的最大值 另外nginx.conf中设置上传文件大小 upload_max_file_size软限制 client_max_body_size硬限制

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

Nginx 动静分离架构(学习笔记九)

一、原理 Nginx 动静分离简单来说就是把动态跟静态请求分开,不能理解成只是单纯的把动态页面和静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开,可以理解成使用Nginx 处理静态页面,Tomcat、 Resin 出来动态页面。动静分离从目前实现角度来讲大致分为两种, 一种是纯粹把静态文件独立成单独的域名,放在独立的服务器上,也是目前主流推崇的方案; 另外一种方法就是动态跟静态文件混合在一起发布,通过 nginx 来分开。这样也是本次课程要讲解的,具体怎么来实现呢, 通过 location 指定不同的后缀名实现不同的请求转发。通过 expires 参数设置,可以使浏览器缓存过期时间,减少与服务器之前的请求和流量。具体 Expires 定义:是给一个资源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可,所以不会产生额外的流量。此种方法非常适合不经常变动的资源。(如果经常更新的文件,不建议使用 Expires 来缓存),我这里设置 3d,表示在这 3 天之内访问这个 URL,发送一个请求,比对服务器该文件最后更新时间没有变化,则不会从服务器抓取,返回状态码 304,如果有修改,则直接从服务器重新下载,返回状态码 200。 二、例子 在 nginx-1.13.0.tar.gz下测试 项目 静态文件路径 配置文件 server { listen 80; server_name a; location /t1 { proxy_pass http://192.168.56.90:8180/t1; } location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css)$ { root /usr/local/tomcat/apache-tomcat-7.0.70-8180/webapps/; expires 30d; } } 测试 访问静态文件 再次访问 状态为304 最后检查 Nginx 配置是否正确即可,然后测试动静分离是否成功,之需要删除后端tomcat 服务器上的某个静态文件,查看是否能访问,如果可以访问说明静态资源 nginx 直接返回了,不走后端 tomcat 服务器

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

Spring Boot 学习笔记 - 钢钢更新

背景介绍 该文档是在慕课网实战课程《Spring Boot企业微信点餐系统》基础上总结而成,旨在记录Spring Boot一些相关知识,文章中涉及的代码都经过验证,可以直接使用。该文档作为个人参考资料,会长期更新。 慕课网课程地址:Spring Boot企业微信点餐系统 数据库设计 微信点餐数据库 - SQL.md -- 类目 create table `product_category` ( `category_id` int not null auto_increment, `category_name` varchar(64) not null comment '类目名字', `category_type` int not null comment '类目编号', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`category_id`) ); -- 商品 create table `product_info` ( `product_id` varchar(32) not null, `product_name` varchar(64) not null comment '商品名称', `product_price` decimal(8,2) not null comment '单价', `product_stock` int not null comment '库存', `product_description` varchar(64) comment '描述', `product_icon` varchar(512) comment '小图', `product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架', `category_type` int not null comment '类目编号', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`product_id`) ); -- 订单 create table `order_master` ( `order_id` varchar(32) not null, `buyer_name` varchar(32) not null comment '买家名字', `buyer_phone` varchar(32) not null comment '买家电话', `buyer_address` varchar(128) not null comment '买家地址', `buyer_openid` varchar(64) not null comment '买家微信openid', `order_amount` decimal(8,2) not null comment '订单总金额', `order_status` tinyint(3) not null default '0' comment '订单状态, 默认为新下单', `pay_status` tinyint(3) not null default '0' comment '支付状态, 默认未支付', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`order_id`), key `idx_buyer_openid` (`buyer_openid`) ); -- 订单商品 create table `order_detail` ( `detail_id` varchar(32) not null, `order_id` varchar(32) not null, `product_id` varchar(32) not null, `product_name` varchar(64) not null comment '商品名称', `product_price` decimal(8,2) not null comment '当前价格,单位分', `product_quantity` int not null comment '数量', `product_icon` varchar(512) comment '小图', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`detail_id`), key `idx_order_id` (`order_id`) ); -- 卖家(登录后台使用, 卖家登录之后可能直接采用微信扫码登录,不使用账号密码) create table `seller_info` ( `id` varchar(32) not null, `username` varchar(32) not null, `password` varchar(32) not null, `openid` varchar(64) not null comment '微信openid', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`id`) ) comment '卖家信息表'; Spring Boot项目结构 POM依赖 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.imooc</groupId> <artifactId>sell</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sell</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>cn.springboot</groupId> <artifactId>best-pay-sdk</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-starter-cache</artifactId>--> <!--</dependency>--> </dependencies> <build> <finalName>sell</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 应用配置 全局配置文件 application.yml spring: profiles: active: dev 开发配置文件 application-dev.yml spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false jpa: show-sql: true jackson: default-property-inclusion: non_null redis: host: 192.168.30.113 port: 6379 server: context-path: /sell #logging: # pattern: # console: "%d - %msg%n" ## path: /var/log/tomcat/ # file: /var/log/tomcat/sell.log # level: # com.imooc.LoggerTest: debug wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx keyPath: /var/weixin_cert/h5.p12 notifyUrl: http://sell.natapp4.cc/sell/pay/notify templateId: orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ projectUrl: wechatMpAuthorize: http://sell.natapp4.cc wechatOpenAuthorize: http://sell.natapp4.cc sell: http://sell.natapp4.cc logging: level: com.imooc.dataobject.mapper: trace mybatis: mapper-locations: classpath:mapper/*.xml 生产配置文件 application-prod.yml spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false jackson: default-property-inclusion: non_null redis: host: 192.168.30.113 port: 6379 server: context-path: /sell #logging: # pattern: # console: "%d - %msg%n" ## path: /var/log/tomcat/ # file: /var/log/tomcat/sell.log # level: # com.imooc.LoggerTest: debug wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx keyPath: /var/weixin_cert/h5.p12 notifyUrl: http://sell.natapp4.cc/sell/pay/notify templateId: orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ projectUrl: wechatMpAuthorize: http://sell.natapp4.cc wechatOpenAuthorize: http://sell.natapp4.cc sell: http://sell.natapp4.cc logging: level: com.imooc.dataobject.mapper: trace mybatis: mapper-locations: classpath:mapper/*.xml 配置文件类 BlogProperties.java package com.mindex.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @ConfigurationProperties(prefix = "com.mindex.blog") @Component public class BlogProperties { private String name; private String desc; } 引用配置信息 BlogPropertiesTest.java package com.mindex.config; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class BlogPropertiesTest { @Autowired private BlogProperties blogProperties; @Test public void getProperties() throws Exception { Assert.assertEquals("轮子王", blogProperties.getName()); Assert.assertEquals("用行动改变世界", blogProperties.getDesc()); } } 自定义配置文件 WechatAccountConfig.java package com.imooc.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Map; @Data @Component @ConfigurationProperties(prefix = "wechat") public class WechatAccountConfig { private String mpAppId; private String mpAppSecret; private String openAppId; private String openAppSecret; private String mchId; private String mchKey; private String keyPath; private String notifyUrl; private Map<String, String> templateId; } 引用自定义的配置文件 WechatMpConfig.java package com.imooc.config; import me.chanjar.weixin.mp.api.WxMpConfigStorage; import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class WechatMpConfig { @Autowired private WechatAccountConfig accountConfig; @Bean public WxMpService wxMpService() { WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage()); return wxMpService; } @Bean public WxMpConfigStorage wxMpConfigStorage() { WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage(); wxMpConfigStorage.setAppId(accountConfig.getMpAppId()); wxMpConfigStorage.setSecret(accountConfig.getMpAppSecret()); return wxMpConfigStorage; } } 日志处理 SLF4j Logback logback-spring.xml <?xml version="1.0" encoding="UTF-8" ?> <configuration> <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern> %d - %msg%n </pattern> </layout> </appender> <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>DENY</onMatch> <onMismatch>ACCEPT</onMismatch> </filter> <encoder> <pattern> %msg%n </pattern> </encoder> <!--滚动策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--路径--> <fileNamePattern>/Users/kwang/imooc/sell/log/info.%d.log</fileNamePattern> </rollingPolicy> </appender> <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <encoder> <pattern> %msg%n </pattern> </encoder> <!--滚动策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--路径--> <fileNamePattern>/Users/kwang/imooc/sell/log/error.%d.log</fileNamePattern> </rollingPolicy> </appender> <root level="info"> <appender-ref ref="consoleLog" /> <appender-ref ref="fileInfoLog" /> <appender-ref ref="fileErrorLog" /> </root> </configuration> Swagger2 文档工具 引入POM依赖 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> 创建Swagger配置类 package com.mindex.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagger2Configuration { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() // 自行修改为自己的包路径 .apis(RequestHandlerSelectors.basePackage("com.mindex.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("api文档") .description("Restful 风格接口") .version("1.0") .build(); } } 在controller中引入Swagger注解 package com.mindex.controller; import com.mindex.entities.User; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController @RequestMapping(value = "/users") @Api(value = "/users", tags = "测试接口模块") public class UserController { //创建线程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @ApiOperation(value = "获取用户列表", notes = "") @RequestMapping(value = "/", method = RequestMethod.GET) public List<User> getUserList() { // 处理"/users/"的GET请求,用来获取用户列表 // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递 List<User> userList = new ArrayList<User>(users.values()); return userList; } @ApiOperation(value = "创建用户", notes = "根据User对象创建用户") @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") @RequestMapping(value = "/", method = RequestMethod.POST) public String postUser(@ModelAttribute User user) { // 处理"/users/"的POST请求,用来创建User // 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数 users.put(user.getId(), user); return "success"; } @ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUser(@PathVariable Long id) { // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息 // url中的id可通过@PathVariable绑定到函数的参数中 return users.get(id); } @ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") }) @RequestMapping(value = "/{id}", method = RequestMethod.POST) public String putUser(@PathVariable Long id, @ModelAttribute User user) { // 处理"/users/{id}"的PUT请求,用来更新User信息 User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { // 处理"/users/{id}"的DELETE请求,用来删除User users.remove(id); return "success"; } } 启动tomcat查看文档 http://localhost:8080/swagger-ui.html IDEA插件 JRebel插件 引入POM依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>1.5.1.RELEASE</version> <scope>provided</scope> </dependency> 配置Application.java @SpringBootApplication @ComponentScan(basePackages = "com.mindex") public class Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 配置maven project选项 选中Lifecycle-clean及compile 安装JRebel插件 配置JRebel插件 在IDEA里新建一个部署配置项。 运行测试 Lombok插件 好处:安装了Lombok插件和pom引用依赖后,可以简化代码,例如:无需再写get/set/toString方法,打印日志时直接使用log关键字等。 安装Lombok插件 安装步骤在这里 引用pom依赖 <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> 好处1:只要使用@Data、@Getter、@Setter、@ToString等注解,无需再写繁琐的get/set/toString方法,Lombok会在编译时自动加入代码。 package com.imooc.dataobject; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.util.Date; @Entity @DynamicUpdate @Data public class ProductCategory { @Id @GeneratedValue private Integer categoryId; private String categoryName; private Integer categoryType; private Date createTime; private Date updateTime; public ProductCategory(String categoryName, Integer categoryType) { this.categoryName = categoryName; this.categoryType = categoryType; } public ProductCategory() { } } 好处2:输出日志时,可以直接使用log关键字输出,支持参数引用。 package com.imooc; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = LoggerTest.class) @Slf4j public class LoggerTest { // 无需再写LoggerFactory // private final Logger logger = LoggerFactory.getLogger(LoggerTest.class); @Test public void test1() { String name = "imooc"; String password = "12345"; log.debug("debug..."); log.info("name: {}, password: {}", name, password); log.error("error..."); log.warn("warning..."); } } 项目运行类(主入口) SellApplication.java package com.imooc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SellApplication { public static void main(String[] args) { SpringApplication.run(SellApplication.class, args); } } enums枚举类 ResultEnum.java package com.imooc.enums; import lombok.Getter; @Getter public enum ResultEnum { PARAM_ERROR(1,"参数不正确"), PRODUCT_NOT_EXIST(10, "商品不存在"), PRODUCT_STOCK_ERROR(11, "库存不正确"), ORDER_NOT_EXIST(12, "订单不存在"), ; private Integer code; private String message; ResultEnum(Integer code, String message) { this.code = code; this.message = message; } } util工具类 可以把常用的方法放在util包里,比如拼接vo视图、生成唯一编码等; 构造结果VO视图 ResultVOUtil.java package com.imooc.utils; import com.imooc.VO.ResultVO; public class ResultVOUtil { public static ResultVO success(Object object) { ResultVO resultVO = new ResultVO(); resultVO.setData(object); resultVO.setCode(0); resultVO.setMsg("成功"); return resultVO; } public static ResultVO success() { return success(null); } public static ResultVO error(Integer code, String msg) { ResultVO resultVO = new ResultVO(); resultVO.setCode(code); resultVO.setMsg(msg); return resultVO; } } 生成随机id KeyUtil.java package com.imooc.utils; import java.util.Random; public class KeyUtil { /** * 生成唯一的主键 * 格式: 时间+随机数 * * @return */ public static synchronized String genUniqueKey() { Random random = new Random(); Integer number = random.nextInt(900000) + 100000; return System.currentTimeMillis() + String.valueOf(number); } } object -> json JsonUtil.java package com.imooc.utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class JsonUtil { public static String toJson(Object object) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); return gson.toJson(object); } } Cookie工具类 CookieUtil.java package com.imooc.utils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; public class CookieUtil { /** * 设置 * @param response * @param name * @param value * @param maxAge */ public static void set(HttpServletResponse response, String name, String value, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setPath("/"); cookie.setMaxAge(maxAge); response.addCookie(cookie); } /** * 获取cookie * @param request * @param name * @return */ public static Cookie get(HttpServletRequest request, String name) { Map<String, Cookie> cookieMap = readCookieMap(request); if (cookieMap.containsKey(name)) { return cookieMap.get(name); }else { return null; } } /** * 将cookie封装成Map * @param request * @return */ private static Map<String, Cookie> readCookieMap(HttpServletRequest request) { Map<String, Cookie> cookieMap = new HashMap<>(); Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie: cookies) { cookieMap.put(cookie.getName(), cookie); } } return cookieMap; } } 比较金额(double类型)是否相等 MathUtil.java package com.imooc.utils; public class MathUtil { private static final Double MONEY_RANGE = 0.01; /** * 比较2个金额是否相等 * @param d1 * @param d2 * @return */ public static Boolean equals(Double d1, Double d2) { Double result = Math.abs(d1 - d2); if (result < MONEY_RANGE) { return true; }else { return false; } } } VO视图层 要返回的数据格式如下: 第一层VO ResultVO.java package com.imooc.VO; import lombok.Data; /** * http请求返回的最外层对象 */ @Data public class ResultVO<T> { /* 状态码 */ private Integer code; /* 提示信息 */ private String msg; /* 具体内容 */ private T data; } 第二层VO ProductVO.java package com.imooc.VO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * 商品(包含类目) */ @Data public class ProductVO { @JsonProperty("name") private String categoryName; @JsonProperty("type") private Integer categoryType; @JsonProperty("foods") private List<ProductInfoVO> productInfoVOList; } 第三层VO ProductInfoVO.java package com.imooc.VO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.math.BigDecimal; /** * 商品详情 */ @Data public class ProductInfoVO { @JsonProperty("id") private String productId; @JsonProperty("name") private String productName; @JsonProperty("price") private BigDecimal productPrice; @JsonProperty("description") private String productDescription; @JsonProperty("icon") private String productIcon; } DTO层 可以把DTO理解成数据库视图。 OrderDTO.java package com.imooc.dto; import com.imooc.dataobject.OrderDetail; import lombok.Data; import java.math.BigDecimal; import java.util.Date; import java.util.List; @Data public class OrderDTO { List<OrderDetail> orderDetailList; private String orderId; private String buyerName; private String buyerPhone; private String buyerAddress; private String buyerOpenid; private BigDecimal orderAmount; private Integer orderStatus; private Integer payStatus; private Date createTime; private Date updateTime; } CartDTO.java package com.imooc.dto; import lombok.Data; @Data public class CartDTO { private String productId; private Integer productQuantity; public CartDTO(String productId, Integer productQuantity) { this.productId = productId; this.productQuantity = productQuantity; } } Exception异常处理 自定义异常 SellException.java package com.imooc.exception; import com.imooc.enums.ResultEnum; public class SellException extends RuntimeException { private Integer code; public SellException(ResultEnum resultEnum) { super(resultEnum.getMessage()); this.code = resultEnum.getCode(); } public SellException(Integer code, String message) { super(message); this.code = code; } } ResponseBankException.java package com.imooc.exception; public class ResponseBankException extends RuntimeException { } SellerAuthorizeException.java package com.imooc.exception; public class SellerAuthorizeException extends RuntimeException { } 自定义异常处理器 SellExceptionHandler.java package com.imooc.handler; import com.imooc.VO.ResultVO; import com.imooc.config.ProjectUrlConfig; import com.imooc.exception.ResponseBankException; import com.imooc.exception.SellException; import com.imooc.exception.SellerAuthorizeException; import com.imooc.utils.ResultVOUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice public class SellExceptionHandler { @Autowired private ProjectUrlConfig projectUrlConfig; //拦截登录异常 //http://sell.natapp4.cc/sell/wechat/qrAuthorize?returnUrl=http://sell.natapp4.cc/sell/seller/login @ExceptionHandler(value = SellerAuthorizeException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public ModelAndView handlerAuthorizeException() { return new ModelAndView("redirect:" .concat(projectUrlConfig.getWechatOpenAuthorize()) .concat("/sell/wechat/qrAuthorize") .concat("?returnUrl=") .concat(projectUrlConfig.getSell()) .concat("/sell/seller/login")); } @ExceptionHandler(value = SellException.class) @ResponseBody public ResultVO handlerSellerException(SellException e) { return ResultVOUtil.error(e.getCode(), e.getMessage()); } @ExceptionHandler(value = ResponseBankException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public void handleResponseBankException() { } } 统一异常处理 假设访问一个不存在的页面,抛出异常 package com.mindex.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class ThymeleafTest { @ResponseBody @RequestMapping("/hello") public String hello() throws Exception { throw new Exception("发生错误"); } } 创建全局异常处理类 通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中。 package com.mindex.exception; import com.mindex.entities.ErrorInfo; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } } 实现error.html页面展示:在templates目录下创建error.html,将请求的URL和Exception对象的message输出。 <!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head lang="en"> <meta charset="UTF-8"/> <title>统一异常处理</title> </head> <body> <h1>Error Handler</h1> <div th:text="${url}"></div> <div th:text="${exception.message}"></div> </body> </html> 启动该应用,访问:http://localhost:8080/hello,可以看到如下错误提示页面。 通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在@ControllerAdvice类中,根据抛出的具体Exception类型匹配@ExceptionHandler中配置的异常类型来匹配错误映射和处理。 返回json格式 创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据。 package com.mindex.entities; import lombok.Data; @Data public class ErrorInfo<T> { public static final Integer OK = 0; public static final Integer ERROR = 100; private Integer code; private String message; private String url; private T data; } 创建一个自定义异常,用来实验捕获该异常,并返回json。 package com.mindex.exception; public class MyException extends Exception { public MyException(String message) { super(message); } } Controller中增加json映射,抛出MyException异常。 package com.mindex.controller; import com.mindex.exception.MyException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/json") public String json() throws MyException { throw new MyException("发生错误2"); } } 为MyException异常创建对应的处理。 @ExceptionHandler(value = MyException.class) @ResponseBody public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception { ErrorInfo<String> r = new ErrorInfo<>(); r.setMessage(e.getMessage()); r.setCode(ErrorInfo.ERROR); r.setData("Some Data"); r.setUrl(req.getRequestURL().toString()); return r; } 启动应用,访问:http://localhost:8080/json,可以得到如下返回内容: Authorize用户有效性鉴定 SellerAuthorizeAspect.java package com.imooc.aspect; import com.imooc.constant.CookieConstant; import com.imooc.constant.RedisConstant; import com.imooc.exception.SellerAuthorizeException; import com.imooc.utils.CookieUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @Aspect @Component @Slf4j public class SellerAuthorizeAspect { @Autowired private StringRedisTemplate redisTemplate; @Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" + "&& !execution(public * com.imooc.controller.SellerUserController.*(..))") public void verify() { } @Before("verify()") public void doVerify() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //查询cookie Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN); if (cookie == null) { log.warn("【登录校验】Cookie中查不到token"); throw new SellerAuthorizeException(); } //去redis里查询 String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue())); if (StringUtils.isEmpty(tokenValue)) { log.warn("【登录校验】Redis中查不到token"); throw new SellerAuthorizeException(); } } } Data Object层(Entity) 主要用来映射数据库表及字段关系。 dataobject定义 ProductCategory.java package com.imooc.dataobject; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.util.Date; @Entity @DynamicUpdate @Data public class ProductCategory { @Id @GeneratedValue private Integer categoryId; private String categoryName; private Integer categoryType; private Date createTime; private Date updateTime; public ProductCategory(String categoryName, Integer categoryType) { this.categoryName = categoryName; this.categoryType = categoryType; } public ProductCategory() { } } Repository层 JpaRepository对数据库常用操作进行了封装,通过继承JpaRepository可以快速实现数据库操作。JpaRepository第一个参数是data object,第二个是该data object的主键。 repository定义 ProductCategoryRepository.java package com.imooc.repository; import com.imooc.dataobject.ProductCategory; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer> { List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); } repository单元测试 ProductCategoryRepositoryTest.java package com.imooc.repository; import com.imooc.dataobject.ProductCategory; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class ProductCategoryRepositoryTest { @Autowired private ProductCategoryRepository repository; @Test public void findOneTest() { ProductCategory productCategory = repository.findOne(1); System.out.println(productCategory.toString()); } @Test @Transactional public void saveTest() { ProductCategory productCategory = new ProductCategory("男生最爱", 4); ProductCategory result = repository.save(productCategory); Assert.assertNotNull(result); // Assert.assertNotEquals(null, result); } @Test public void findByCategoryTypeInTest() { List<Integer> list = Arrays.asList(2,3,4); List<ProductCategory> result = repository.findByCategoryTypeIn(list); Assert.assertNotEquals(0, result.size()); } @Test public void updateTest() { // ProductCategory productCategory = repository.findOne(4); // productCategory.setCategoryName("男生最爱1"); ProductCategory productCategory = new ProductCategory("男生最爱", 4); ProductCategory result = repository.save(productCategory); Assert.assertEquals(productCategory, result); } } Service层 service接口 CategoryService.java package com.imooc.service; import com.imooc.dataobject.ProductCategory; import java.util.List; public interface CategoryService { ProductCategory findOne(Integer categoryId); List<ProductCategory> findAll(); List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); ProductCategory save(ProductCategory productCategory); } service实现 需要使用@Service注解 CategoryServiceImpl.java package com.imooc.service.impl; import com.imooc.dataobject.ProductCategory; import com.imooc.repository.ProductCategoryRepository; import com.imooc.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class CategoryServiceImpl implements CategoryService { @Autowired private ProductCategoryRepository repository; @Override public ProductCategory findOne(Integer categoryId) { return repository.findOne(categoryId); } @Override public List<ProductCategory> findAll() { return repository.findAll(); } @Override public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) { return repository.findByCategoryTypeIn(categoryTypeList); } @Override public ProductCategory save(ProductCategory productCategory) { return repository.save(productCategory); } } service单元测试 CategoryServiceImplTest.java package com.imooc.service.impl; import com.imooc.dataobject.ProductCategory; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class CategoryServiceImplTest { @Autowired private CategoryServiceImpl categoryService; @Test public void findOne() throws Exception { ProductCategory productCategory = categoryService.findOne(1); Assert.assertEquals(new Integer(1), productCategory.getCategoryId()); } @Test public void findAll() throws Exception { List<ProductCategory> productCategoryList = categoryService.findAll(); Assert.assertNotEquals(0, productCategoryList.size()); } @Test public void findByCategoryTypeIn() throws Exception { List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(Arrays.asList(1,2,3,4)); Assert.assertNotEquals(0, productCategoryList.size()); } @Test public void save() throws Exception { ProductCategory productCategory = new ProductCategory("男生专享", 10); ProductCategory result = categoryService.save(productCategory); Assert.assertNotNull(result); } } Controller层 @RestController 注解,直接返回json格式;@RequestMapping("buyer/product") 注解声明服务的url前缀; BuyerProductController.java package com.imooc.controller; import com.imooc.VO.ProductInfoVO; import com.imooc.VO.ProductVO; import com.imooc.VO.ResultVO; import com.imooc.dataobject.ProductCategory; import com.imooc.dataobject.ProductInfo; import com.imooc.service.CategoryService; import com.imooc.service.ProductService; import com.imooc.utils.ResultVOUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @RestController @RequestMapping("/buyer/product") public class BuyerProductController { @Autowired private ProductService productService; @Autowired private CategoryService categoryService; @GetMapping("/list") public ResultVO list() { //1. 查询所有上架商品 List<ProductInfo> productInfoList = productService.findUpAll(); //2. 查询类目(一次性查询) //传统方法 List<Integer> categoryTypeList = new ArrayList<>(); // for (ProductInfo productInfo : productInfoList) { // categoryTypeList.add(productInfo.getCategoryType()); // } //精简方法(java8, lambda) categoryTypeList = productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList()); List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList); //3. 数据拼装 List<ProductVO> productVOList = new ArrayList<>(); for (ProductCategory productCategory : productCategoryList) { ProductVO productVO = new ProductVO(); productVO.setCategoryName(productCategory.getCategoryName()); productVO.setCategoryType(productCategory.getCategoryType()); List<ProductInfoVO> productInfoVOList = new ArrayList<>(); for (ProductInfo productInfo : productInfoList) { if (productInfo.getCategoryType().equals(productCategory.getCategoryType())) { ProductInfoVO productInfoVO = new ProductInfoVO(); BeanUtils.copyProperties(productInfo, productInfoVO); productInfoVOList.add(productInfoVO); } } productVO.setProductInfoVOList(productInfoVOList); productVOList.add(productVO); } return ResultVOUtil.success(productVOList); } } SellerProductController.java package com.imooc.controller; import com.imooc.dataobject.ProductCategory; import com.imooc.dataobject.ProductInfo; import com.imooc.exception.SellException; import com.imooc.form.ProductForm; import com.imooc.service.CategoryService; import com.imooc.service.ProductService; import com.imooc.utils.KeyUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import javax.validation.Valid; import java.util.List; import java.util.Map; @Controller @RequestMapping("/seller/product") public class SellerProductController { @Autowired private ProductService productService; @Autowired private CategoryService categoryService; /** * 列表 * @param page * @param size * @param map * @return */ @GetMapping("/list") public ModelAndView list(@RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "size", defaultValue = "10") Integer size, Map<String, Object> map) { PageRequest request = new PageRequest(page - 1, size); Page<ProductInfo> productInfoPage = productService.findAll(request); map.put("productInfoPage", productInfoPage); map.put("currentPage", page); map.put("size", size); return new ModelAndView("product/list", map); } /** * 商品上架 * @param productId * @param map * @return */ @RequestMapping("/on_sale") public ModelAndView onSale(@RequestParam("productId") String productId, Map<String, Object> map) { try { productService.onSale(productId); } catch (SellException e) { map.put("msg", e.getMessage()); map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/error", map); } map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/success", map); } /** * 商品下架 * @param productId * @param map * @return */ @RequestMapping("/off_sale") public ModelAndView offSale(@RequestParam("productId") String productId, Map<String, Object> map) { try { productService.offSale(productId); } catch (SellException e) { map.put("msg", e.getMessage()); map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/error", map); } map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/success", map); } @GetMapping("/index") public ModelAndView index(@RequestParam(value = "productId", required = false) String productId, Map<String, Object> map) { if (!StringUtils.isEmpty(productId)) { ProductInfo productInfo = productService.findOne(productId); map.put("productInfo", productInfo); } //查询所有的类目 List<ProductCategory> categoryList = categoryService.findAll(); map.put("categoryList", categoryList); return new ModelAndView("product/index", map); } /** * 保存/更新 * @param form * @param bindingResult * @param map * @return */ @PostMapping("/save") // @Cacheable(cacheNames = "product", key = "123") // @Cacheable(cacheNames = "product", key = "456") // @CachePut(cacheNames = "product", key = "123") @CacheEvict(cacheNames = "product", allEntries = true, beforeInvocation = true) public ModelAndView save(@Valid ProductForm form, BindingResult bindingResult, Map<String, Object> map) { if (bindingResult.hasErrors()) { map.put("msg", bindingResult.getFieldError().getDefaultMessage()); map.put("url", "/sell/seller/product/index"); return new ModelAndView("common/error", map); } ProductInfo productInfo = new ProductInfo(); try { //如果productId为空, 说明是新增 if (!StringUtils.isEmpty(form.getProductId())) { productInfo = productService.findOne(form.getProductId()); } else { form.setProductId(KeyUtil.genUniqueKey()); } BeanUtils.copyProperties(form, productInfo); productService.save(productInfo); } catch (SellException e) { map.put("msg", e.getMessage()); map.put("url", "/sell/seller/product/index"); return new ModelAndView("common/error", map); } map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/success", map); } } 使用Mabatis注解方式实现增删改查 mapper层 ProductCategoryMapper.java package com.imooc.dataobject.mapper; import com.imooc.dataobject.ProductCategory; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; public interface ProductCategoryMapper { @Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{category_type, jdbcType=INTEGER})") int insertByMap(Map<String, Object> map); @Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{categoryType, jdbcType=INTEGER})") int insertByObject(ProductCategory productCategory); @Select("select * from product_category where category_type = #{categoryType}") @Results({ @Result(column = "category_id", property = "categoryId"), @Result(column = "category_name", property = "categoryName"), @Result(column = "category_type", property = "categoryType") }) ProductCategory findByCategoryType(Integer categoryType); @Select("select * from product_category where category_name = #{categoryName}") @Results({ @Result(column = "category_id", property = "categoryId"), @Result(column = "category_name", property = "categoryName"), @Result(column = "category_type", property = "categoryType") }) List<ProductCategory> findByCategoryName(String categoryName); @Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}") int updateByCategoryType(@Param("categoryName") String categoryName, @Param("categoryType") Integer categoryType); @Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}") int updateByObject(ProductCategory productCategory); @Delete("delete from product_category where category_type = #{categoryType}") int deleteByCategoryType(Integer categoryType); ProductCategory selectByCategoryType(Integer categoryType); } mapper单元测试 ProductCategoryMapperTest.java package com.imooc.dataobject.mapper; import com.imooc.dataobject.ProductCategory; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class ProductCategoryMapperTest { @Autowired private ProductCategoryMapper mapper; @Test public void insertByMap() throws Exception { Map<String, Object> map = new HashMap<>(); map.put("categoryName", "师兄最不爱"); map.put("category_type", 101); int result = mapper.insertByMap(map); Assert.assertEquals(1, result); } @Test public void insertByObject() { ProductCategory productCategory = new ProductCategory(); productCategory.setCategoryName("师兄最不爱"); productCategory.setCategoryType(102); int result = mapper.insertByObject(productCategory); Assert.assertEquals(1, result); } @Test public void findByCategoryType() { ProductCategory result = mapper.findByCategoryType(102); Assert.assertNotNull(result); } @Test public void findByCategoryName() { List<ProductCategory> result = mapper.findByCategoryName("师兄最不爱"); Assert.assertNotEquals(0, result.size()); } @Test public void updateByCategoryType() { int result = mapper.updateByCategoryType("师兄最不爱的分类", 102); Assert.assertEquals(1, result); } @Test public void updateByObject() { ProductCategory productCategory = new ProductCategory(); productCategory.setCategoryName("师兄最不爱"); productCategory.setCategoryType(102); int result = mapper.updateByObject(productCategory); Assert.assertEquals(1, result); } @Test public void deleteByCategoryType() { int result = mapper.deleteByCategoryType(102); Assert.assertEquals(1, result); } @Test public void selectByCategoryType() { ProductCategory productCategory = mapper.selectByCategoryType(101); Assert.assertNotNull(productCategory); } } Dao层 ProductCategoryDao.java package com.imooc.dataobject.dao; import com.imooc.dataobject.mapper.ProductCategoryMapper; import org.springframework.beans.factory.annotation.Autowired; import java.util.Map; public class ProductCategoryDao { @Autowired ProductCategoryMapper mapper; public int insertByMap(Map<String, Object> map) { return mapper.insertByMap(map); } } 对象转换 Form表单对象(Json)转成DTO OrderForm2OrderDTOConverter.java package com.imooc.converter; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.imooc.dataobject.OrderDetail; import com.imooc.dto.OrderDTO; import com.imooc.enums.ResultEnum; import com.imooc.exception.SellException; import com.imooc.form.OrderForm; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; @Slf4j public class OrderForm2OrderDTOConverter { public static OrderDTO convert(OrderForm orderForm) { Gson gson = new Gson(); OrderDTO orderDTO = new OrderDTO(); orderDTO.setBuyerName(orderForm.getName()); orderDTO.setBuyerPhone(orderForm.getPhone()); orderDTO.setBuyerAddress(orderForm.getAddress()); orderDTO.setBuyerOpenid(orderForm.getOpenid()); List<OrderDetail> orderDetailList = new ArrayList<>(); try { orderDetailList = gson.fromJson(orderForm.getItems(), new TypeToken<List<OrderDetail>>() { }.getType()); } catch (Exception e) { log.error("【对象转换】错误, string={}", orderForm.getItems()); throw new SellException(ResultEnum.PARAM_ERROR); } orderDTO.setOrderDetailList(orderDetailList); return orderDTO; } } Data object转DTO OrderMaster2OrderDTOConverter.java package com.imooc.converter; import com.imooc.dataobject.OrderMaster; import com.imooc.dto.OrderDTO; import org.springframework.beans.BeanUtils; import java.util.List; import java.util.stream.Collectors; public class OrderMaster2OrderDTOConverter { public static OrderDTO convert(OrderMaster orderMaster) { OrderDTO orderDTO = new OrderDTO(); BeanUtils.copyProperties(orderMaster, orderDTO); return orderDTO; } public static List<OrderDTO> convert(List<OrderMaster> orderMasterList) { return orderMasterList.stream().map(e -> convert(e) ).collect(Collectors.toList()); } } 中文字符乱码 application.properties spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8 IDEA设置 网页模板 Thymeleaf pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> ... <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> index.html <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head lang="en"> <meta charset="UTF-8"/> <title></title> </head> <body> <h1 th:text="${host}">Hello World</h1> </body> </html> 注意:模板的位置可以直接放在src/main/resources/templates/目录下。 application.properties spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8 # Enable template caching. spring.thymeleaf.cache=true # Check that the templates location exists. spring.thymeleaf.check-template-location=true # Content-Type value. spring.thymeleaf.content-type=text/html # Enable MVC Thymeleaf view resolution. spring.thymeleaf.enabled=true # Template encoding. spring.thymeleaf.encoding=UTF-8 # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.excluded-view-names= # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.mode=HTML5 # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.prefix=classpath:/templates/ # Suffix that gets appended to view names when building a URL. spring.thymeleaf.suffix=.html ThymeleafTest.java package com.mindex.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ThymeleafTest { @RequestMapping("/") public String index(ModelMap map) { map.addAttribute("host", "http://www.mindex.com"); return "index"; } } 运行结果如下: 数据库操作 JdbcTemplate 引入POM依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency> 修改配置文件application.properties spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=welcome spring.datasource.driver-class-name=com.mysql.jdbc.Driver Service接口:UserService.java package com.mindex.service; public interface UserService { void create(String name, Integer age); void deleteByName(String name); Integer getAllUsers(); void deleteAllUsers(); } Service实现:UserServiceImpl.java package com.mindex.service.impl; import com.mindex.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; @Override public void create(String name, Integer age) { jdbcTemplate.update("INSERT INTO user (name, age) VALUES (?,?);", name, age); } @Override public void deleteByName(String name) { jdbcTemplate.update("delete from USER where NAME = ?", name); } @Override public Integer getAllUsers() { return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class); } @Override public void deleteAllUsers() { jdbcTemplate.update("delete from USER"); } } 单元测试:UserServiceImplTest.java package com.mindex.service.impl; import com.mindex.service.UserService; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class UserServiceImplTest { @Autowired private UserService userService; @Test public void create() throws Exception { // 插入5个用户 userService.create("a", 1); userService.create("b", 2); userService.create("c", 3); userService.create("d", 4); userService.create("e", 5); Assert.assertEquals(5, userService.getAllUsers().intValue()); } @Test public void deleteByName() throws Exception { userService.deleteByName("b"); userService.deleteByName("c"); Assert.assertEquals(3, userService.getAllUsers().intValue()); } @Test public void getAllUsers() throws Exception { } @Test public void deleteAllUsers() throws Exception { } } Spring-data-jpa 引入pom依赖:pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 在application.properties创建数据库连接信息。 spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123qweasd spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下: create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。 create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。 update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。 validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 创建实体创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto,在应用启动的时候框架会自动去数据库中创建对应的表。 User.java package com.mindex.entities; import lombok.Data; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Data @Entity public class User { @Id @GeneratedValue private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } public User() { } } 创建数据访问接口下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:UserRepository.java package com.mindex.repository; import com.mindex.entities.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface UserRepository extends JpaRepository<User, Long> { User findByName(String name); User findByNameAndAge(String name, Integer age); @Query("from User u where u.name=:name") User findUser(@Param("name") String name); } 在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。 下面对上面的UserRepository做一些解释,该接口继承自JpaRepository,通过查看JpaRepository接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。 在我们实际开发中,JpaRepository接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。 在上例中,我们可以看到下面两个函数: User findByName(String name) User findByNameAndAge(String name, Integer age) 它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。 除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。 Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开,这里先挖个坑,后续再补文章填坑,如您对这些感兴趣可以关注我博客或简书,同样欢迎大家留言交流想法。 单元测试在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。 package com.mindex.repository; import com.mindex.entities.User; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test public void test() throws Exception { // 创建10条记录 userRepository.save(new User("AAA", 10)); userRepository.save(new User("BBB", 20)); userRepository.save(new User("CCC", 30)); userRepository.save(new User("DDD", 40)); userRepository.save(new User("EEE", 50)); userRepository.save(new User("FFF", 60)); userRepository.save(new User("GGG", 70)); userRepository.save(new User("HHH", 80)); userRepository.save(new User("III", 90)); userRepository.save(new User("JJJ", 100)); // 测试findAll, 查询所有记录 Assert.assertEquals(10, userRepository.findAll().size()); // 测试findByName, 查询姓名为FFF的User Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue()); // 测试findUser, 查询姓名为FFF的User Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue()); // 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName()); // 测试删除姓名为AAA的User userRepository.delete(userRepository.findByName("AAA")); // 测试findAll, 查询所有记录, 验证上面的删除是否成功 Assert.assertEquals(9, userRepository.findAll().size()); } } 集成Redis 手动配置集成Redis 引入POM依赖:pom.xml <!--此处并不是直接使用spring提供的redis-starter--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> 外部配置文件:application.yaml jedis: host: 127.0.0.1 port: 6379 pool: max-idle: 300 min-idle: 10 max-total: 600 max-wait: 1000 block-when-exhausted: true Java配置类(替代传统xml):RedisConfig.java package com.mindex.config; import lombok.Data; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Data @Component public class RedisConfig { @Bean("jedis.config") public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.min-idle}") int minIdle, @Value("${jedis.pool.max-idle}") int maxIdle, @Value("${jedis.pool.max-wait}") int maxWaitMillis, @Value("${jedis.pool.block-when-exhausted}") boolean blockWhenExhausted, @Value("${jedis.pool.max-total}") int maxTotal) { JedisPoolConfig config = new JedisPoolConfig(); config.setMinIdle(minIdle); config.setMaxIdle(maxIdle); config.setMaxWaitMillis(maxWaitMillis); config.setMaxTotal(maxTotal); // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true config.setBlockWhenExhausted(blockWhenExhausted); // 是否启用pool的jmx管理功能, 默认true config.setJmxEnabled(true); return config; } @Bean public JedisPool jedisPool(@Qualifier("jedis.config") JedisPoolConfig config, @Value("${jedis.host}") String host, @Value("${jedis.port}") int port) { return new JedisPool(config, host, port); } } Service接口定义:RedisService.java package com.mindex.service; public interface RedisService { String get(String key); boolean set(String key, String val); } Service接口实现类:RedisServiceImpl.java package com.mindex.service.impl; import com.mindex.service.RedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @Service public class RedisServiceImpl implements RedisService { // 此处直接注入即可 @Autowired private JedisPool jedisPool; @Override public String get(String key) { Jedis jedis = this.jedisPool.getResource(); String ret; try { ret = jedis.get(key); } finally { if (jedis != null) jedis.close(); } return ret; } @Override public boolean set(String key, String val) { Jedis jedis = this.jedisPool.getResource(); try { return "OK".equals(jedis.set(key, val)); } finally { if (jedis != null) jedis.close(); } } } 测试:RedisServiceImplTest.java package com.mindex.service.impl; import com.mindex.service.RedisService; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class RedisServiceImplTest { @Autowired private RedisService redisService; @Test public void testGet() { // test set boolean status = this.redisService.set("foo", "bar"); Assert.assertTrue(status); // test get String str = this.redisService.get("foo"); Assert.assertEquals("bar", str); } } 在Redis中检查结果 使用spring-boot-starter-data-redis 外部配置文件:application.yaml jedis: host: 127.0.0.1 port: 6379 pool: max-idle: 300 min-idle: 10 max-total: 600 max-wait: 1000 block-when-exhausted: true 配置类:RedisConfig1.java package com.mindex.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig1 { @Autowired private RedisConnectionFactory redisConnectionFactory; /** * 实例化 RedisTemplate 对象 * */ @Bean public RedisTemplate<String, Object> functionDomainRedisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); this.initRedisTemplate(redisTemplate, redisConnectionFactory); return redisTemplate; } /** * 序列化设置 */ private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setConnectionFactory(factory); } @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } @Bean public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForValue(); } @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } @Bean public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForZSet(); } } 简单测试:RedisAnotherConfigTest.java package com.mindex; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @RunWith(SpringRunner.class) @SpringBootTest public class RedisAnotherConfigTest { @Autowired private ValueOperations<String, Object> valueOperations; @Autowired private RedisTemplate<String, Object> redisTemplate; @Test public void contextLoads() { } @Test public void testStringOps() { this.valueOperations.set("k1", "spring-redis"); Boolean hasKey = this.redisTemplate.hasKey("k1"); assertEquals(true, hasKey); Object str = this.valueOperations.get("k1"); assertNotNull(str); assertEquals("spring-redis", str.toString()); } } 邮件 使用JavaMailSender发送邮件 引入POM依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> 增加配置:application.properties 注意:这里的password是邮箱授权码,不是邮箱密码。 spring.mail.host=smtp.qq.com spring.mail.username=85648606@qq.com spring.mail.password=clqpsraiifwqbidg spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true 单元测试:MailTest.java package com.mindex; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class MailTest { @Autowired private JavaMailSender mailSender; @Test public void sendSimpleMail() throws Exception { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom("85648606@qq.com"); message.setTo("wfgdlut@msn.com"); message.setSubject("主题:简单邮件"); message.setText("测试邮件内容"); mailSender.send(message); } } 发送html格式邮件 @Test public void sendAttachmentsMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage); mimeMessageHelper.setFrom("85648606@qq.com"); mimeMessageHelper.setTo("wfgdlut@msn.com"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); } 发送包含内嵌图片的邮件 /** * 发送包含内嵌图片的邮件 * * @throws Exception */ @Test public void sendAttachedImageMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setFrom("85648606@qq.com"); mimeMessageHelper.setTo("wfgdlut@msn.com"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>"); // cid为固定写法,imageId指定一个标识 sb.append("<img src=\"cid:imageId\"/></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置imageId FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png")); mimeMessageHelper.addInline("imageId", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); } 发送包含附件的邮件 /** * 发送包含附件的邮件 * @throws Exception */ @Test public void sendAttendedFileMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8"); mimeMessageHelper.setFrom("85648606@qq.com"); mimeMessageHelper.setTo("wfgdlut@msn.com"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置附件 FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png")); mimeMessageHelper.addAttachment("test.png", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); }

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

Oracle DBA 增值 PostgreSQL,Greenplum 学习计划

标签 PostgreSQL , Oracle , Greenplum 背景 去O很大程度上是国家层面的战略考虑,比如斯诺登事件,最近贸易战的“中兴”事件,使得去O成为一个不可不做的事情。 但是去O喊了若干年,并没有真正意义上成为轻松愉快的全民运动,比较大的原因可能是 1、去O的成本 2、去O后的责任方 3、利益 随着云计算的兴起,第2,3点应该可以得到很好的解决(有问题找O转换为有问题找云厂商)。而去O的成本就成为一个比较痛的点,到底需要花多少人力物力可以完成去O,过去基本上都是拍脑袋的,每个项目因人而异。同时大部分的成本又来自于对应用的改造。(因为目标库不具备Oracle兼容性,需要大改) 不过成本的问题,也已经解决,阿里云RDSPG,基于PostgreSQL的Oracle兼容版PPAS,不仅在“功能、性能、可靠性、扩展性”等方面可以满足业务的需

资源下载

更多资源
优质分享App

优质分享App

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

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

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

用户登录
用户注册