赞
踩
(1)Maven环境变量
变量名:M2_HOME
变量值:D:\install\maven\apache-maven-3.8.0
找到Path在环境变量值尾部加入:;%M2_HOME%\bin; //前面注意分号
(2)maven的环境变量是否配置成功
打开dos窗口运行命令mvn -v,出现如下图所示的信息说明安装成功;
(3)修改本地仓库位置(如果不想修改本地仓库位置则这一步骤省略即可)
Maven会将下载的类库(jar包)放置到本地的一个目录下(一般默认情况下maven在本机的仓库位于C:\我的文档中.m2.\repository),如果想重新定义这个目录的位置就需要修改Maven本地仓库的配置:
a.在自己喜欢的位置创建文件夹,此处本人创建的位置是(D:/maven-repository)
b.在安装Maven的目录下找到conf文件夹,在文件夹中找到settings.xml文件,打开并修改localRepository的值,如图:
(4)修改Maven的下载镜像地址为阿里源
安装好Maven时,要及时的修改Maven下载的镜像地址,最好改为国内的下载镜像,例如阿里云中央仓库,华为云中央仓库。
同样打开conf文件夹中的setting.xml文件,找到,注释掉已有的,改为下面的这段代码即可
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
这个配置可以在阿里云官方配置上看到对应的配置内容
修改完后,再次搭建Maven项目,下载一个大型项目的jar大约10S左右,开发体验度瞬间飙升。
(5)在Idea上配置Maven工具
当我们已经新建了项目,需要退出在管理主界面再配置,这样可以解决配置只针对当前项目的问题
打开IDEA,点击File–> Close Project(关闭所有打开项目,进入到管理首页):
关闭项目后出现如下界面,点击 Customize -->All settings(设置所有):
在弹出的设置界面搜索maven,并点击override,apply,ok
上述修改应该已经解决了,若不行,可使用如下备用方案:,修改其默认文件夹配置,路径为:
C:\Users\Administrator\AppData\Roaming\JetBrains\IntelliJIdea2022.3\options\project.default.xml文件
(6)配置解决证书等问题
-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true -DarchetypeCatalog=internal
这些设置最好都在ide管理首页对所有项目进行设置。
(1)修改字体风格和大小
(2)优化导入包
(3)提示不区分大小写
(4)编码设置为utf-8
![请添加图片描述](https://img-blog.csdnimg.cn/9707355b11bf417ea186cbc7dbb3bd11.png
(5)类复制为json的第三方插件(方便以后开发时候右击复制类为json格式)
使用时如下
{
"id": 0,
"userId": 0,
"title": "",
"summary": "",
"readCount": 0,
"email": ""
}
(6)json数据快速生成类对象插件
使用时,先复制一个json格式数据,然后鼠标放在java文件中的类名上,按Alt+Insert,并选择Gsonformat
在弹出的框中,粘贴json文件,并点击ok,最终生成该类
new project 》Maven Archetype
这种情况是ide没有识别到你的sources文件和tests文件,需要自己手动指定。project structure面板,手动指定main文件为sources文件和test文件为tests文件,然后点击apply即可。指定完后工程项目中的main文件和test颜色会变成蓝色和绿色
原因是ide默认编译版本为5,需要修改为自己使用的jdk版本,
sout:System.out.println();
变量名.sout:System.out.println(变量名);
(1)switch中 “case 值->” 和“ case 值:” 不能混用,一个switch块中只能有一种语法格式。
(2)switch作为表达式,赋值给变量需要用yield或case 值->表达式。
(3)文本块
文本块用"""三个双引开始和结束,内容不能与三个双引位于同一行。它属于字符串,可以用+和equals(),substring()等。
String str= """
hello world
[]
df
""";
String str1= """
name:%s
phone:%d
""".formatted("xx",10);
文本块的其实对其方式是以每一行最左边的字符对齐,可用函数str.indent(5);控制前面的空格数。
函数:string stripIndent();删除每行开头和结尾空白
(4)var变量,声明时必须赋初值。
(5)sealed关键字,密闭类型,用于限制继承,可修饰类定义或接口定义,与permits关键字连用。其修饰的类或接口只能被permits后的类继承或实现。子类可用三个关键字修饰final、non-sealed、sealed。
a).final修饰表示到此结束,不能再被继承或实现;
b).non-sealed修饰表示到此后变成可无限继承;
c).sealed修饰表示该子类也是密闭类,需要与permits连用指定继承的子类;
public sealed class Shape permits Circle,Square, Rectangle { private Integer width; private Integer height; public void draw(){ System.out.println("画一个图形shape"); } } public final class Circle extends Shape { @Override public void draw() { System.out.println("图形circle"); } } public non-sealed class Rectangle extends Shape{ @Override public void draw() { System.out.println("rectangle"); } } public sealed class Square extends Shape permits RoundSquare { }
使用ide嵌入的spring脚手架即可。
web项目模块文件概述:
启动类作为扫描的起点,扫描同级目录的所有文件并完成对应注解的容器注入
(1)在pom.xml中build下添加finalName标签,表示打包后的架包名字
<build>
<!--打包后架包名-->
<finalName>myweb</finalName>
<plugins>
<plugin>
···
可以显示定义下导出的文件类型名,一般不写会自动默认
<groupId>com.sgl</groupId>
<artifactId>Lession02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!--导出文件类型-->
<packaging>jar</packaging>
<name>Lession02</name>
<description>Lession02</description>
然后点击maven,先clean,在package导出
导出的jar包会在target文件夹中出现。普通jar和springboot的jar区别:
(1)文件类型介绍
其支持properties和yaml(yml)格式配置文件,properties是java中常用的配置文件格式,key=value。key唯一,文件扩展名为properties。
yaml也是一种配置文件格式,语法为 key:[空格]值。其扩展名为yaml或yml
(2)文件说明
sping boot一般只使用一种格式的配置文件,若两种格式都有,则properties文件优先。
application配置文件的名称和文字都可以修改,约定名称为application,位置在resources目录
yml格式文件是用换行表示"."效果:
app:
name: lession
owner: sgl
port: 8001
新增的其他配置文件需要放在resources中,可创建一个conf文件夹管理保存
(3)多文件配置
通常若需要把其他配置文件(如数据库配置文件)放在其他配置文件,那么主配置文件需要将其引入
#=========application.yml #默认项 key: 值 #app.name app: name: lession owner: sgl port: 8001 #导入其他文件的配置,多个文件用“,”作为分隔符 spring: config: import: conf/db.yml,conf/redis.yml #=======db.yml spring: datasource: url: jdbc:mysql://localhost:3306/db user: root password: root
获取方法和默认配置文件获取一样,或通过Environment类获取
@Value("${app.port}")
private Integer port;
@Value("${spring.datasource.url}")
private String db_url;
(4)总结
(5)快速属性类创建
注意:快速配置时候,需要在启动类Application类中允许配置类
@EnableConfigurationProperties(AppBean.class)
当属性文件属性值比较多,不用一个一个注解,直接统一注解类即可。注意配置文件key值和属性名称要一致,该类需要get、set方法。prefix表示只配置前缀为app的key,其他不管;
注意:类属性为非静态
若嵌套了,则需要嵌套bean,如下图,security应是一个类,注解配置再主类即可。
(6)数组、列表、结合的属性配置
(7)指定源配置文件配置
需要指定@propertySource注解,如下图:该文件在resources目录下,文件名为group-info.properties
要使用aop,需要在pom文件中添加依赖
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(1)通过切片方式实现日志打印
代码配置实现在调用函数前打印日志信息。
@Component @Aspect public class LogAspect { //功能增加的方法为:com.sgl.project.service下的所有包,任意类的任意方法和参数 @Before("execution(* com.sgl.project.service..*.*(..))") public void sysLog(JoinPoint jp){ //用|分隔的以{}开头和结尾 StringJoiner log=new StringJoiner("|","{","}"); DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); log.add(formatter.format(LocalDateTime.now())); //当前执行的业务方法名称 String methodName=jp.getSignature().getName(); log.add(methodName); //方法参数 Object[] args=jp.getArgs(); for (Object arg:args) { log.add(arg==null?"-":arg.toString()); } System.out.println("日志:"+log); } }
(1)引入lombok
在pom.xml中添加依赖,或ide创建项目时选择引入了lombok,依赖大致如下:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
(2)lombok常用注解
@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@AllArgsConstructor : 注在类上,提供类的全参构造
@NoArgsConstructor : 注在类上,提供类的无参构造
@Setter : 注在属性上,提供 set 方法
@Getter : 注在属性上,提供 get 方法
@EqualsAndHashCode : 注在类上,提供对应的 equals 和 hashCode 方法
@Log4j/@Slf4j : 注在类上,提供对应的 Logger 对象,变量名为 log
(3)使用示例
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ArticlePO {
private Integer id;
private Integer userId;
private String title;
private String summary;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
虽然方便使用,但不能任意几个参数的构造,且不能对set、get方法添加控制
(1)引入JdbcTemplate
创建项目是选择依赖SQL->MySQL Driver,或直接在pom中引入依赖
(2)配置ide数据源
数据源不配置应该只是不能在ide中直接看到数据库内容,不影响代码执行。(好像配置了数据源后代码中字段名会有代码提示,但是我没试到)
按上图打开数据源配置界面,然后按下图配置自己的数据源,并测试连接
(3)配置代码连接的数据源
在resources/application文件中配置数据源信息(根据文件类型配置,目前是properties格式)
#配置数据源
#这句是可以不写的,会自动识别
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/sglblog
spring.datasource.username=root
spring.datasource.password=root
#设置执行数据库脚本 alaways:总是执行(每次运行都执行数据库脚本);never:不执行脚本
spring.sql.init.mode=never
最后一句是用来控制代码创建数据库是否开启的。正常在resources文件下的schema.sql和data.sql会被程序默认用来创建表和数据,数据库需要提前创建好。
配置数据源后即可使用,下面是JdbcTemplate的常用方法
(4)查询语句
使用lombok配置对应bean后,即可使用
在使用JdbcTemplate地方先注册,再使用
下面实例中有map和list的新遍历方式
@Autowired private JdbcTemplate jdbcTemplate; @Test void test01() { String sql="select count(*) as ct from article"; Long count=jdbcTemplate.queryForObject(sql,Long.class); System.out.println("count = " + count); } //使用一条记录,使用?作为占位符 @Test void test02() { String sql="select * from article where id=?"; ArticlePO articlePO= jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(ArticlePO.class),1); System.out.println("articlePO = " + articlePO); } @Test void testList() { String sql="select * from article order by id"; List<Map<String,Object>> listMap=jdbcTemplate.queryForList(sql); listMap.forEach(ls->{ ls.forEach((key,value)->{ System.out.println(key+":"+value); }); }); } @Test void testUpdate() { String sql="update article set title=? where id=?"; int rows= jdbcTemplate.update(sql,"二哥头",1); System.out.println("rows = " + rows); } @Test void testQueryConten() { String sql= """ select m.*,d.id as detail_id,d.article_id,d.content from article m join article_detail d on m.id=d.article_id where m.id=1 """; List< ArticleMainPO> mainList= jdbcTemplate.query(sql,(rs,num)->{ var id= rs.getInt("id"); var user_id= rs.getInt("user_id"); var title= rs.getString("title"); var summary= rs.getString("summary"); var read_count= rs.getInt("read_count"); var create_time=new Timestamp(rs.getTimestamp("create_time").getTime()).toLocalDateTime(); var update_time=new Timestamp(rs.getTimestamp("update_time").getTime()).toLocalDateTime(); //文章内容 var content= rs.getString("content"); var detail_id= rs.getInt("detail_id"); var article_id= rs.getInt("article_id"); ArticleDetailPO detailPO=new ArticleDetailPO(detail_id,article_id,content); return new ArticleMainPO(id,user_id,title,summary,read_count,create_time,update_time,detailPO); }); mainList.forEach(m->{ System.out.println("m.getId()="+m.getId()); System.out.println("detial="+m.getDetail()); }); }
MyBatis需要依赖mysql驱动 、mybatis的starter
首先需要配置属性文件,然后可开始使用,单表增删改查(CRUD)
属性文件配置如下(由于还是用的mysql驱动,所以配置与jdbcTemplate是一样的):
#配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/sglblog
spring.datasource.username=root
spring.datasource.password=root
#配置mybatis
#支持驼峰命名,下划线
mybatis.configuration.map-underscore-to-camel-case=true
#日志(控制台输出sql语句等)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
然后需要在程序入口调用mybatis的标签,让代码自动扫描配置
(1)单表查询
若查询后不需要做更改,直接在接口中就能实现,注意查询结果与类属性的对应即可,可让表的列名与类的属性名一样或互为驼峰对应。
注意:mybatis3.4以后,不需要在使用@param注解占位了,直接变量名和语句中名字一样就行。
public interface ArticleMapper { //按主键查询(当数据库表列名和类的属性名一致时,会自动完成赋值) //当在配置文件配置了运行驼峰命名那么下划线和驼峰都会默认相同,如user_id与userId一致 @Select(""" select id,user_id,title,summary,read_count,create_time,update_time from article where id=#{articleId} """) //让查询结果与类直接对应有几种方式 //1.查询结果集字段名与类的属性名直接对应,可在查询中用as或其他方式(配置了驼峰命名,则更宽泛一些) //2.自己定义结果集与类的对应关系(在同一个类里面结果集可通过@ResultMap进行复用,从而减少代码) // 本类中若其他函数结果集和这个一样(如返回List<ArticlePO>),可不声明@Result,而是使用如下标签 // @ResultMap("BaseArticleMap") @Results(id="BaseArticleMap",value = { //是否是主键(默认false),结果列名,类属性名 @Result(id=true,column = "id",property = "id"), @Result(column ="user_id",property = "userId"), @Result(column = "title",property = "title"), @Result(column = "summary",property = "summary"), @Result(column = "read_count",property = "readCount"), @Result(column = "create_time",property = "createTime"), @Result(column = "update_time",property = "updateTime"), }) ArticlePO selectById(@Param("articleId")Integer id); //mybatis3.4以后可改写为: ArticlePO selectById(Integer articleId); }
//调用是先注册,在调用即可
@Autowired
private ArticleMapper articleMapper;
@Test
void testQueryObj() {
ArticlePO articlePO=articleMapper.selectById(1);
System.out.println("articlePO = " + articlePO);
}
@Autowired需要注册多个属性时,可用直接在类上用@RequiredArgsConstructor注解,就自动注册了该类的所有属性。
(2)增删改
//insert //由于有个主键id为自动增加,所以新增时into article后不太好省略表的列名,不然得吧把id也要传值 @Insert(""" insert into article(user_id,title,summary,read_count,create_time,update_time) values(#{userId},#{title},#{summary},#{readCount},#{createTime},#{updateTime}) """) int insertArticle(ArticlePO po); //update @Update(""" update article set read_count=#{readCount} where id=#{id} """) int updateReadCount(Integer id,Integer readCount); //delete @Delete(""" delete from article where id=#{id} """) int deleteById(Integer id);
(3)小结
ResultMap的xml使用方式如下:
新建一个Map
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.sgl.mybatis.mapper.ArticleMapper"> <!--定义resultMap--> <resultMap id="sglArticleMapper" type="com.sgl.mybatis.po.ArticlePO"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="title" property="title"/> <result column="summary" property="summary"/> <result column="read_count" property="readCount"/> <result column="create_time" property="createTime"/> <result column="update_time" property="updateTime"/> </resultMap> </mapper>
然后在配置文件中定义mapper文件位置
注意:SpringBoot项目的classpath包含三个:
a)src/main/java路径
b)src/main/resouces路径
c)第三方jar包的根路径
#指定自定义mapper文件的位置(mapper文件夹下的任意文件夹中的任意xml文件)
mybatis.mapper-locations=classpath:/mappers/**/*.xml
使用时候直接使用resultMap的id指定即可
@Select("""
select id,user_id,title,summary,read_count,create_time,update_time
from article where id=#{articleId}
""")
@ResultMap("sglArticleMapper")
ArticlePO selectById(@Param("articleId")Integer id);
(4)SQL提供者(增删改查四个提供者)
使用提供者,可以把对数据库的操作语句都放在一个提供者类中,然后使用@SelectProvider注解
注:可使用多个提供者类来使用,如把四类语句写在4个类等。
//创建提供者类,注意方法要用静态 public class SqlProvider { //定义静态方法 public static String selectArticle(){ return "select * from article where id=#{id}"; } public static String updateSql(){ return "update article set update_time=#{newTime} where id=#{id}"; } } //使用提供者 @SelectProvider(type= SqlProvider.class,method = "selectArticle") @ResultMap("sglArticleMapper")//使用xml形式的map ArticlePO selectByProvider(Integer id); @UpdateProvider(type = SqlProvider.class,method = "updateSql") int updateProvider(Integer id, LocalDateTime newTime); //insert语句和之前的一直,mybatis会通过名字自动解析类属性对应到字段 @InsertProvider(type = SqlProvider.class,method = "insertSql") int insertProvider(ArticlePO po);
(5)@One一对一查询
//类定义 //注意:ArticleMainPO 对应的表其实没有最后一个detail项。 public class ArticleMainPO { private Integer id; private Integer userId; private String title; private String summary; private Integer readCount; private LocalDateTime createTime; private LocalDateTime updateTime; private ArticleDetailPO detail;//一对一 } public class ArticleDetailPO { private Integer id; private Integer articleId; private String content; } //使用 @Select(""" select * from article_detail where article_id=#{articleId} """) ArticleDetailPO selectDetail(Integer articleId); @Select(""" select * from article where id=#{id} """) //定义结果集时,可以不用全部都写出对应,只有特殊的才需要单独声明 //一对一中参数数目:数据库哪个列,对应类的那个属性,获取方式one,然后通过什么方法获取,获取类型:懒加载 //这样可以由查询主表,直接根据关联条件,查询到关联表 @Results({ @Result(column = "id",property = "detail", one=@One(select = "com.sgl.mybatis.mapper.ArticleMapper.selectDetail",fetchType = FetchType.LAZY)) }) ArticleMainPO selectAllArticle(Integer id);
(6)@Many一对多查询
public class ArticleEntity { private Integer id; private Integer userId; private String title; private String summary; private Integer readCount; private LocalDateTime createTime; private LocalDateTime updateTime; //多个评论 List<CommentPO> comments; } //使用:与one类似 @Select(""" select * from comment where article_id=#{articleId} """) List<CommentPO> selectComments(Integer articleId); @Select(""" select * from article where id=#{id} """) @Results({ @Result(column = "id",property = "comments", many=@Many(select = "com.sgl.mybatis.mapper.ArticleMapper.selectComments",fetchType = FetchType.LAZY)) }) ArticleEntity selectAllComment(Integer id);
(7)mybatis其他配置方式
mybatis可在application中配置,也可以把其配置单独放在xml文件,然后在application中引入xml文件即可:
//application中配置mybatis文件路径为:同级目录中的mybatis-config.xml文件
mybatis.config-location=classpath:mybatis-config.xml
然后可参照官网属性进行配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--配置驼峰为true-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</configuration>
默认连接池为HikariCP,具体配置可参看HikariCP官网上的说明
#默认连接池,可修改为其他的,比如Tomcat,DBCP等
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
事务回滚需要在内入口出添加开启事务管理注解(默认已开)
//开始事务管理(默认就开启了,不加该注解也行)
@EnableTransactionManagement
//扫描mapper接口的位置
@MapperScan(basePackages ="com.sgl.mybatis.mapper")
@SpringBootApplication
public class MyBatisStudyApplication {...}
/** * @Transactional:事务控制注解 * 位置:1.方法上;2.类上 * 放在方法上更好些,类上表示类的所有方法都是事务 * 事务回滚: * 1.默认对运行时异常,执行回滚rollback * 2.rollbackFor:需要回滚的异常列表: * @Transactional(rollbackFor = {IOException.class}) */ @Transactional @Override public boolean postNewArticle(ArticlePO article, String content) { //新增文章 int rows=articleMapper.insert_sw_Article(article); //抛出异常 if(article.getReadCount()<1){ //throw的异常为抛出异常,会进行事务回滚 throw new RuntimeException("文章数量需不能小于1"); } //添加文章内容 ArticleDetailPO detailPO=new ArticleDetailPO(); detailPO.setArticleId(article.getId()); detailPO.setContent(content ); int dRows=articleMapper.insert_sw_detail(detailPO); return (rows+dRows)==2?true:false; }
事务不能执行的情况:
(1)非事务方法调用事务方法,则事务处理将失去作用,即不会回滚;但事务方法A调用事务方法B,B中的事务处理正常执行;
(2)事务方法中若创建线程操作则处于线程中的事务处理无效;
spring web依赖,自带了json库,Tomcat、mvc等,方便web开发
(1)返回html模型视图,给特定jsp(html)使用
thymeleaf视图文件默认放在resources/templates中,controller中返回的模型,只要名字与视图名一致,会自动解析,下图展示一个返回html视图和对应数据model
顺便说:resources/static用于存放静态资源,如图片,文件等。静态资源只要放在resources文件进行,推荐放在static目录
<!--quick.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="margin-left: 200px"> <h3>视图测试</h3> <div th:text="${title}"></div> <div th:text="${time}"></div> </div> </body> </html>
(2)返回json视图(即json数据)
@RequestMapping("exam/json") public void responseJson(HttpServletResponse res) throws IOException { String json="{\"name\":\"展示\",\"age\":20}"; //应答,通过HttpServletResponse输出 res.setContentType("application/json;charset=utf-8"); PrintWriter out=res.getWriter(); out.println(json); out.flush(); out.close(); } @RequestMapping("/exam/json1") //这个注解会使用jackson架包,自动把类数据转换为json字符串数据,并返回,相当于完成了上面的out功能 @ResponseBody public User getUserInfo(){ User user=new User(); user.setUsername("就辅导费"); user.setAge(12); return user; }
注意:类注解为@RestController,方法注解无需 @ResponseBody即可返回string、对象等;
但若类注解为@Controller,方法上不加@ResponseBody,则返回的的string只能是对应的视图,没有则报错,若想正常返回,需要如上所示,使用PrintWriter对象输出。
所以最好直接使用注解、
(3)请求方式
(1)Controller
@RestController推荐使用,而@Controller已经不推荐使用了。
springboot中路径匹配默认为PahtPatternParser模型,若要修改,需要在application配置文件中配置;
(2)接收参数
A)、用参数和对象接收参数
get请求时“?”后的参数可通过方法上的参数名对应接收,也可通过类变量接收(类的属性名和参数变量名相同则接收成功,不相同则为null),类的参数需要有set、get方法和无参数构造方法。
B)、用HttpServletRequest接收参数
@GetMapping("/param/p3")
@ResponseBody
public String param3(HttpServletRequest request){
String name=request.getParameter("name");
String age=request.getParameter("age");
return name+","+age;
}
C)、用@RequestParam接收参数
D)、获取请求头里的参数
E)、使用@RequestBody接收数据(post、json)
注意,在ide自带的请求测试中:
###回车:表示一个新的请求;
请求url回车相连的行用于设置header;
空一行后,用于输入请求体RequestBody
F)、使用Reader、InputStream读取post请求体的数据
//使用Reader、InputStream读取post请求体的数据
@PostMapping("/param/json2")
@ResponseBody
public String p6(Reader reader){
StringBuffer content=new StringBuffer("");
try (BufferedReader bin=new BufferedReader(reader)){
var line="";
while ((line=bin.readLine())!=null){
content.append(line);
}
}catch (IOException e){
e.printStackTrace();
}
return "p7="+content.toString();
}
G)、数组类型接收
get请求接收,post感觉不太行,应该直接用json
(2)参数验证
可自己加判断验证,也可用定义好的属性注解,给bean的属性做验证。
首先需要添加校验依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Data public class Article { private Integer id; @NotNull(message = "必须有作者") private Integer userId; @NotBlank(message = "必需有标题") //@Size认为null是有效值 @Size(min=3,max=30,message = ">3,<20") private String title; @NotBlank(message = "必需有副标题") @Size(min=3,max=30,message = ">5,<60") private String summary; @DecimalMin(value = "0",message = "不能小于0") private Integer readCount; //邮箱格式验证,没有加非空验证,这个验证时email要么为"",要么格式正确 @Email(message = "不合格邮箱格式") private String email; } //=======使用========== //发布新文章 //@Validated 验证bean //BindingResult 错误结果绑定(包含bean的验证结果) @PostMapping("/article/add") public Map<String, Object> addArticle(@Validated @RequestBody Article article, BindingResult br){ //service 方法处理文字业务 //返回结果 Map<String,Object> map=new HashMap<>(); if(br.hasErrors()){ List<FieldError> fieldErrors=br.getFieldErrors(); fieldErrors.forEach(field->{ map.put(field.getField(),field.getDefaultMessage()); }); } return map; }
参数分类验证
当一些情况时需要验证,一些则不需要验证(如添加不需要验证id,修改则需要id非空)
首先在bean中声明分类接口,并给属性附上:
//组就是接口名
public static interface AddArticleGroup{};
public static interface EditArticleGroup{};
@NotNull(message = "id不为空",groups = {EditArticleGroup.class})
@Min(value = 1,message = "id>0",groups = {EditArticleGroup.class})
private Integer id;
@NotNull(message = "必须有作者",
groups = {AddArticleGroup.class,EditArticleGroup.class})
private Integer userId;
然后使用时也指定目前是使用哪种情况接口即可:
public Map<String, Object> addArticle(@Validated(Article.AddArticleGroup.class) @RequestBody Article article, BindingResult br){
//service 方法处理文字业务
//...
}
(3)页面视图View
需要有对应的视图html文件,视图使用的方式:
a)、使用Model作为参数,放回视图名
//使用sping框架的Model
import org.springframework.ui.Model;
@RequestMapping("/exam/quick")
public String quick(Model model){
//调用service,处理请求,获取数据
model.addAttribute("title","算法");
model.addAttribute("time", LocalDateTime.now());
//request.setAttribute("title","算法");
//指定一个视图,显示数据
return "quick";//它是视图文件的名称
}
b)、使用ModelAndView作为返回值
@GetMapping("/hello")
public ModelAndView hello(){
//ModelAndView 表示数据和视图
ModelAndView mv=new ModelAndView();
mv.addObject("name","信息");
mv.addObject("age",10);
mv.setViewName("hello");
return mv;
}
c)、返回json并指定response状态
@RequestMapping("/exam/json5")
@ResponseBody
public ResponseEntity<User> returnEntity(){
User user=new User();
user.setUsername("就辅导费");
user.setAge(12);
ResponseEntity<User> response=new ResponseEntity<>(user, HttpStatus.NOT_FOUND);
return response;
}
在application.properties中可进行如下配置
#路径匹配策略(2.6以上版本默认就是path_pattern_parser策略) spring.mvc.pathmatch.matching-strategy=path_pattern_parser #配置服务器 server.port=8001 #项目路径名 server.servlet.context-path=/api #request,response字符编码 server.servlet.encoding.charset=utf-8 #强制request,response设置charset字符编码 server.servlet.encoding.force=true #日志路径(tomcat) server.tomcat.accesslog.directory=D:/exam #启用访问日志 server.tomcat.accesslog.enabled=true #日志文件名前缀 server.tomcat.accesslog.prefix=mylog #日志文件日期时间 server.tomcat.accesslog.file-date-format=.yyyy-MM-dd #日志文件名称后缀 server.tomcat.accesslog.suffix=.log #post请求内容最大值,默认2M server.tomcat.max-http-form-post-size=2000000 #服务器最大连接数 server.tomcat.max-connections=8000 #配置DispatherServlet #中央控制器路径访问路径变为/api/course/你的路径 spring.mvc.servlet.path=/course #servlet的加载顺序,越小创建时间越早 spring.mvc.servlet.load-on-startup=0 #时间格式,可以在接受请求参数使用 spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
(1)、用注解方式使用servlet
servlet类要添加注解@WebServlet,用于配置web.xml中servlet名称、路径等信息;
/** * @WebServlet:等同于web.xml中油罐servlet的声明 * <servlet> * <servlet-name>HelloServlet</servlet-name> * <servlet-class>xxxx</servlet-class> * </servlet> * <servlet-mapping> * <url-pattern>/helloServlet</url-pattern> * </servlet-mapping> */ @WebServlet(urlPatterns = "/helloServlet",name="HelloServlet") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); PrintWriter out=resp.getWriter(); out.println("spring中的servlet"); out.flush(); out.close(); } }
然后在启动类中添加@ServletComponentScan注解,用于扫描@WebServlet。
(2)、用编码方式使用servlet
这种方式不需要在启动类添加注解,也不需要给servlet添加注解,但是要创捷ServletRegistrationBean对象注册一个的servlet和其路径等,多个就用多个ServletRegistrationBean。
首先创捷servlet:
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out=resp.getWriter();
out.println("登录servlet");
out.flush();
out.close();
}
}
然后创建注册文件
@Configuration
public class WebAppConfig {
@Bean
public ServletRegistrationBean servletRegistrationBean(){
//创建ServletRegistrationBean 注册登记一个或多个servlet
ServletRegistrationBean registrationBean=new ServletRegistrationBean<>();
registrationBean.setServlet(new LoginServlet());
registrationBean.addUrlMappings("/user/login");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
}
(1)、用注解方式
与servlet类似,需要添加@WebFilter注解,并在启动类扫描该注解
//所有的controller都要经过过滤器
@WebFilter(urlPatterns = "/*")
public class LogFilter implements jakarta.servlet.Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
String uri=((HttpServletRequest)servletRequest).getRequestURI().toString();
System.out.println("过滤器执行了,uri:"+uri);
filterChain.doFilter(servletRequest,servletResponse);
}
}
//在启动类添加注解
@ServletComponentScan(basePackages = "com.sgl.servletstudy")
(2)、用编码方式
与servlet一样,创建FilterRegistrationBean 并注册对应的的filter,然后可以去掉filter类上的注解和启动类上的注解
//就在上一个的WebAppConfig 配置类中添加filter注册方法就行
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(2);//设置顺序
return filterRegistrationBean;
}
(3)过滤类顺序
在注解方式中,默认内名称的字母顺序执行;在编码方式中,在注册时可使用函数setOrder进行控制顺序:
(4)使用spring框架中内置的filter类
使用方式就是编码式
这个默认过滤器好像运行在debug模式,所以要在配置文件application中设置,具体哪个默认过滤器使用情况需要去类定义里面看下。
loggin.level.web=debug
注意:监听器Listerer的使用与Filter一样,不再详述
(1)、页面跳转控制器
(2)、数据转换控制器
声明一个实现Formatter的自定义格式转化器
/** * 将请求参数字符串转换为对象DeviceInfo */ public class DeviceFormatter implements Formatter<DeviceInfo> { @Override public DeviceInfo parse(String text, Locale locale) throws ParseException { DeviceInfo info=null; if (StringUtils.hasText(text)){ String[] items=text.split(";"); info=new DeviceInfo(); info.setItem1(items[0]); info.setItem2(items[1]); info.setItem3(items[2]); info.setItem4(items[3]); info.setItem5(items[4]); } return info; } @Override public String print(DeviceInfo object, Locale locale) { StringJoiner joiner=new StringJoiner("#"); joiner.add(object.getItem1()).add(object.getItem2()) .add(object.getItem3()).add(object.getItem4()) .add(object.getItem5()); return joiner.toString(); } }
然后再实现WebMvcConfigurer中覆盖addFormatters方法
(3)、拦截器
拦截器在调用控制器之前实现拦截,用于权限控制等,如实现zhangshan操作员只能看文章,不能修改、删除。使用方式与其他两个一样。
首先创捷文章controller
@RestController public class ArticleController { @PostMapping("/article/add") public String addArticle(){ return "添加"; } @PostMapping("/article/editor") public String editorArticle(){ return "修改"; } @DeleteMapping("/article/delete") public String deleteArticle(){ return "删除"; } @PostMapping("/article/query") public String queryArticle(){ return "查询"; } }
然后创捷有权限的拦截器
public class AuthInterceptor implements HandlerInterceptor { //假设张三只能查 private static final String COMMON_USER="zhangsan"; //判断登录用户是否有权执行相应动作 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("=====AuthInterceptor权限拦截器===="); //登录用户 String loginUser=request.getParameter("loginUser"); //获取请求的uri地址 String requestUri=request.getRequestURI(); //判断用户是否有权操作 if(COMMON_USER.equals(loginUser)&&( requestUri.startsWith("/article/add")|| requestUri.startsWith("/article/editor")|| requestUri.startsWith("/article/delete") )){ return false; } return true; } }
最后登记注册拦截器
多个拦截器,声明多个实现HandlerInterceptor 的类,在登记时用order设置拦截顺序
#上传文件的保存路径
spring.servlet.multipart.location=D://exam
设置分别是:单个文件最大值,一次请求最大值
//传统上传 @PostMapping("/upload") public String upload(HttpServletRequest request){ try { for(Part part:request.getParts()){ String filename=extractFileName(part); //蒋文件写入服务器目录(目录在配置文件中设置) part.write(filename); } }catch (Exception e){ e.printStackTrace(); } return "redirect:/index.html"; } private String extractFileName(Part part){ String contentDisp=part.getHeader("content-disposition"); String[] items=contentDisp.split(";"); for (String s:items) { if(s.strip().startsWith("filename")){ return s.substring(s.indexOf("=")+2,s.length()-1); } } return ""; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="margin-left: 200px"> <h3>上传文件</h3> <form action="upload" enctype="multipart/form-data" method="post"> 选择文件:<input type="file" name="upfile"><br><br> <input type="submit" value="上传"> </form> </div> </body> </html>
spring中的上传
@Controller public class UploadFileController { //上传文件 @PostMapping("/uploadFile") public String uploadFile(@RequestParam("upfile")MultipartFile multipartFile){ System.out.println("开始处理上传文件"); Map<String, Object> info=new HashMap<>(); try { if(!multipartFile.isEmpty()){ info.put("上传文件的参数名字",multipartFile.getName());//upfile info.put("内容类型",multipartFile.getContentType()); var ext="unknow";//文件扩展名 var filename=multipartFile.getOriginalFilename();//原始文件名,如a.jpg if(filename.indexOf(".")>0){ ext=filename.substring(filename.indexOf(".")+1); } //生成服务器使用文件名称 var newFileName= UUID.randomUUID().toString()+"."+ext; var path="D://exam//"+newFileName;//存储服务器文件 //把文件保存到path目录 multipartFile.transferTo(new File(path)); } }catch (Exception e){ e.printStackTrace(); } System.out.println("info = " + info); //重定向到index页面 return "redirect:/index.html"; }
多文件上传,前端多几个type=file,且name一样的input标签;
后端把MultipartFile改为MultipartFile[]
@PostMapping("/files") public String uploadFile(@RequestParam("upfile")MultipartFile[] multipartFiles){ System.out.println("开始处理上传文件"); Map<String, Object> info=new HashMap<>(); try { for (MultipartFile multipartFile:multipartFiles) { if(!multipartFile.isEmpty()){ info.put("上传文件的参数名字",multipartFile.getName());//upfile info.put("内容类型",multipartFile.getContentType()); var ext="unknow";//文件扩展名 var filename=multipartFile.getOriginalFilename();//原始文件名,如a.jpg if(filename.indexOf(".")>0){ ext=filename.substring(filename.indexOf(".")+1); } //生成服务器使用文件名称 var newFileName= UUID.randomUUID().toString()+"."+ext; var path="D://exam//"+newFileName;//存储服务器文件 //把文件保存到path目录 multipartFile.transferTo(new File(path)); } } }catch (Exception e){ e.printStackTrace(); } System.out.println("info = " + info); //重定向到index页面 return "redirect:/index.html"; }
(1)、可以声明一个类来自定义处理所有类型的异常,一种类型只能处理一次。
/** * 1.在类的上面加上@ControllerAdvice,@RestControllerAdvice * 灵活组合@ControllerAdvice和@ResponseBody * 2.在类中自定义方法,处理各种异常 * 方法定义同controller类中的方法定义 */ //控制器功能增加,给controller增加异常处理功能,类似AOP的思想 @ControllerAdvice public class GlobalExceptioinHandler { //定义方法处理数字异常 /** * @exceptionHandler:指定处理异常的方法 * 位置:在方法上面 * 属性:是异常类的class数组,如果你的系统抛出的异常类型于@ExceptionHandler什么的相同,有当前方法处理异常 */ // @ExceptionHandler({ArithmeticException.class}) // public String handlerArthmeticException(ArithmeticException e, Model model){ // String error=e.getMessage(); // model.addAttribute("error",error); // return "exp";//就是试图 // } @ExceptionHandler({ArithmeticException.class}) @ResponseBody public Map<String,String> handlerArthmetic2Exception(ArithmeticException e){ String error=e.getMessage(); Map<String,String> error1=new HashMap<>(); error1.put("msg",e.getMessage()); error1.put("tips","被除数不能为0"); return error1;//返回数据 } }
处理数据校验异常,JRS303
@ExceptionHandler({BindException.class})
@ResponseBody
public Map<String, Object> handlerJSR303Exception(BindException e){
//MethodArgumentNotValidException
System.out.println("===========JSR303===========");
Map<String,Object> map=new HashMap<>();
BindingResult result=e.getBindingResult();
if(result.hasErrors()){
List<FieldError> errors=result.getFieldErrors();
errors.forEach(filed->{
map.put(filed.getField(),filed.getDefaultMessage());
});
}
return map;
}
(2)、使用标准ProblemDetail类
以上两个异常处理函数都是自定义的map作为返回对象,很不规范,不能让所有异常统一,spring提供了异常返回类用于解决这个问题。
运用异常返回类ProblemDetail函数可为
//BookNotFoundException为继承了RuntimeException的类
@ExceptionHandler({BookNotFoundException.class})
@ResponseBody
public ProblemDetail handlerBookNotFoundException(BookNotFoundException e){
ProblemDetail problemDetail=ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND,e.getMessage());
//type:异常类型,是一个uri,uri找到解决问题的途径
problemDetail.setType(URI.create("/api/probs/not-found"));
problemDetail.setTitle("图书异常");
return problemDetail;
}
返回的标准错误示例:
(3)、扩展ProblemDetail
若默认字段不能满足要求,可扩展该类,自定义字段以Map<String,Object>存储,调用setProperty(name,value)将自定义字段添加到ProblemDetail对象中。
@ExceptionHandler({BookNotFoundException.class})
public ErrorResponse handlerException(BookNotFoundException e){
ErrorResponse errorResponse=new ErrorResponseException(HttpStatus.NOT_FOUND,e);
return errorResponse;
}
后记:
要使用最后两种方式处理异常,需要开启支持
#开启支持RFC7807
spring:
mvc:
problemdetails:
enabled: true
感觉用得不多,不再详述。
使用需要使用webClient(webflux)插件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
(1)在该你对象中统一指定访问前缀地址
public interface TodoService { //一个方法就是一个远程服务(远程接口调用) @GetExchange("/todos/{id}") Todo getTodoById(@PathVariable("id") Integer id); @PostExchange(value = "/todos/",accept = MediaType.APPLICATION_JSON_VALUE) Todo createTodo(@RequestBody Todo newTodo); //修改资源 @PutExchange("/todos/1") ResponseEntity<Todo> modifyTodo(@PathVariable Integer id,@RequestBody Todo todo); //删除 @DeleteExchange("todos/{id}") void removeTodo(@PathVariable("id") Integer id); } //代理对象 @Configuration(proxyBeanMethods = false) public class HttpConfig { //创建服务接口的带你对象,基于WebClient @Bean public TodoService requestService(){ WebClient webClient=WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com").build(); //创建带你工厂 HttpServiceProxyFactory httpServiceProxyFactory=HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build(); //创建某个接口的带你服务 return httpServiceProxyFactory.createClient(TodoService.class); } }
(2)使用HttpExchange注解给类设置访问基地址
@HttpExchange(url="https://jsonplaceholder.typicode.com/") public interface AlbumsService { //查询专辑 @HttpExchange(method = "GET",url = "/albums/{id}") Album getById(@PathVariable Integer id); } //同样需要在HttpConfig 中创建代理,只是不在需要写基地址 //创建代理 @Bean public AlbumsService albumsService(){ WebClient webClient=WebClient.create(); //创建带你工厂 HttpServiceProxyFactory httpServiceProxyFactory=HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build(); //创建某个接口的带你服务 return httpServiceProxyFactory.createClient(AlbumsService.class); }
上面是远程接口返回数据到class,同样声明对象为Record也能正常接收远程接口返回的数据。
(3)定义一个通用的代理
可自定义连接超时时长,错误捕获等
//定制http服务代理 @Bean public AlbumsService albumsService(){ //超时设置reactor.netty.http.client.HttpClient; HttpClient httpClient= HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,30000)//连接时间ms .doOnConnected(conn->{ conn.addHandlerLast(new ReadTimeoutHandler(10));//读超时10s conn.addHandlerLast(new WriteTimeoutHandler(10));//写超时 }); WebClient webClient=WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .defaultStatusHandler(HttpStatusCode::isError,clientResponse -> { System.out.println("*************WebClient请求异常*************"); return Mono.error(new RuntimeException("请求异常"+clientResponse.statusCode().value())); }).build(); //创建带你工厂 HttpServiceProxyFactory httpServiceProxyFactory=HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build(); //创建某个接口的带你服务 return httpServiceProxyFactory.createClient(AlbumsService.class); }
类似于JSP的升级
(1)表达式
(2)if-for
(3)thymeleaf配置
基本都是默认配置好,包括文件路径,编码等,也可以自己再配置
注意:thymeleaf使用时,在HTML文件中应该有这行
<html lang="en" xmlns:th="http://www.thymeleaf.org">
注意:通常model文件分为po文件和vo文件,po对应数据库,vo对相应显示的文件类,即po>=vo,可用第三方工具库hutool来实现转换,该插件需要引入依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
使用时大致如下:
List<ArticleVO> articleVOS = BeanUtil.copyToList(listPO, ArticleVO.class);
redis是最常用的缓存数据库,常用于存储用户登录token、临时数据、定时相关数据等。
redis是单线程的,所以redis的操作是原子性的,这样可以保证不会出现并发问题。
redis基于内存,速度非常快,据测试,redis读的速度是110000次/s,写的速度是81000次/s
(1)、redis下载安装配置
redis官网下载并解压,无需安装,直接使用,
运行直接双击redis-server.exe即可
然后再装一个可视化工具
(2)、springboot中配置
引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
并配置:yml
spring:
redis:
host: 192.168.169.133
port: 6379
password: 123456
database: 0
jedis:
pool:
max-active: 8 # 最大连接数
max-wait: 1ms # 最大阻塞时间
max-idle: 4
min-idle: 0
接着写个config类解决下中文存储显示乱码问题
/** * redis配置(解决存储乱码) * 主要是配置Redis的序列化规则,替换默认的jdkSerializer * key的序列化规则用StringRedisSerializer * value的序列化规则用Jackson2JsonRedisSerializer */ @Configuration public class RedisConfig { @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 设置常规key value 的序列化策略 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 这里使用一般的json处理,就不容易存在兼容性问题。否则可能需要对应的json才能解析序列化的数据 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 设置hash类型的序列化策略 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer()); // 注入连接工厂 redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } }
(3)、redis使用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。