Apache Maven是一个项目管理和构建工具,它基于项目对象模型(POM[Project Object Model])的概念,通过一小段描述信息来管理项目的构建
Apache软件基金会,成立于1999年7月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利性组织
官网:http://maven.apache.org/
开源项目:https://www.apache.org/index.html#projects-list
maven项目目录:
main | 实际项目资源 |
---|---|
java | Java源代码目录 |
resources | 配置文件目录 |
test(java,resources) | 测试项目资源 |
pom.xml | 项目配置文件 |
用于存储资源,管理各种jar包
解压apache-maven-3.5.3-bin.zip
配置本地仓库:修改conf/settings.xml中的为一个指定目录
<localRepository>D:\maven\repositorylocalRepository>
配置阿里云私服(用来提高下载速度):修改conf/settings.xml中的标签,为其添加如下子标签:
<mirror>
<id>nexus-aliyunid>
<mirrorOf>*mirrorOf>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
mirror>
配置环境变量:MAVEN_HOME为maven的解压目录,并将其bin目录加入PATH环境变量
方式一:打开IDEA,选择右侧Maven面板,点击+号,选中对应项目的pom.xml文件,双击即可
方式二:打开IDEA,选择File–>Project Structure–>Modules,点击+号,选择import Module,选中对应项目的pom.xml文件,点击apply
如果引入依赖,在本地的仓库不存在,将会连接远程仓库/中央仓库,然后下载依赖
如果不知道依赖的坐标信息,可以到https://mvnrepository.com/中搜索
依赖具有传递性
排除依赖指主动断开依赖的资源,被排出的依赖无需指定版本
<exclusion>
<groupld>被排除的依赖groupld>
<artifactld>被排除的依赖artifactld>
exclusion>
依赖的jar包,默认情况下,可以在任何地方使用,可以通过…设置其作用范围
作用范围:
scope值 | 主程序 | 测试程序 | 打包(运行) | 范例 |
---|---|---|---|---|
compile(默认) | Y | Y | Y | log4j |
test | - | Y | - | junit |
provided | Y | Y | - | servlet-api |
runtime | - | Y | Y | jdbc驱动 |
Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一
Maven中有3套相互独立的生命周期:
每一套生命周期包含一些阶段(phase),阶段是由顺序的,后面的阶段依赖于前面的阶段
生命周期阶段:
执行指定生命周期的两种方式:
GET
、HEAD
、POST
。每种方法规定了客户与服务器联系的类型不同。由于HTTP
协议简单,使得HTTP
服务器的程序规模小,因而通信速度很快。Content-Type
加以标记。Connection: Keep-Alive
实现长连接HTTP
协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快Host | 请求的主机名 |
---|---|
User-Agent | 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0…Chrome/79 |
Accept | 表示浏览器能接收的资源类型 |
Accept-Language | 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页 |
Accept-Encoding | 表示浏览器可以支持的压缩类型,如gzip,deflate等 |
Content-Type | 请求主体的数据类型 |
Content-Length | 请求主体的大小(单位:字节) |
1xx | 响应中-临时状态码,表示请求已经接收,告诉客户端应该继续请求或者 |
---|---|
2xx | 成功-表示请求已经被成功接收,处理已完成 |
3xx | 重定向-重定向到其他地方,让客户端再发起一次请求以完成整个处理 |
4xx | 客户端错误-处理发生错误,责任在客户端,如:请求了不存在的资源,客户端未被授权,禁止访问等 |
5xx | 服务器错误-处理发生错误,责任在服务端,如:程序抛出异常 |
Content-Type | 表示该响应内容的类型,例如text/html,application/json |
---|---|
Content-Length | 表示该响应内容的长度(字节数) |
Content-Encoding | 表示该响应压缩算法,例如gzip |
Cache-Control | 指示客户端应如何缓存,例如max-age=300表示最多缓存300秒 |
Set-Cookie | 告诉浏览器为当前页面所在的域设置cookie |
常见的响应状态码:
状态码 | 英文描述 | 解释 |
---|---|---|
200 | OK | 客户端请求成功,即处理成功 |
302 | Found | 指示所请求的资源已移动到Location响应头给定的URL,浏览器会自动重新访问到这个页面 |
304 | Not Modified | 告诉客户端,所请求的资源至上次取得后,服务端并未更改(隐式重定向) |
400 | Bad Request | 客户端请求有语法错误,不能被服务端所理解 |
403 | Forbidden | 服务器收到请求,但是拒绝提供服务 |
404 | Not Found | 请求资源不存在,一般是URL输入有误,或网站资源被删除 |
405 | Method Not Allowed | 请求方式有误,比如应该用GET请求方式的资源,用来POST |
428 | Precondition Required | 服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头 |
429 | Too Many Requests | 指示用户在给定时间内发送了太多请求(限速),配合Retry-After(多长时间后可以请求)响应头一起使用 |
431 | Request Header Fields Too Large | 请求头太大,服务器不愿意处理请求,因为头部字段太大,请求可以在减少请求头域的大小后重新提交 |
500 | Internal Server Error | 服务器发生不可预期的错误,查看日志 |
503 | Service Unavailable | 服务器尚未准备好处理请求,服务器刚启动,还未初始化好 |
状态码大全:https://cloud.tencent.com/developer/chapter/13553
启动:双击:bin\startup.bat
控制台中文乱码:修改conf/logging.properties
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter
java.util.logging.ConsoleHandler.encoding = (UTF-8)->GBK
关闭:
启动窗口一闪而过:检查JAVA_HOME环境变量是否正确配置
端口号冲突:找到对应程序,将其关掉
配置Tomcat端口号(conf/server.xml)
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号
数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
Result(code,msg,data)
步骤:
Service层及Dao层的实现类,交给IOC容器管理(添加@Component注解)
为Controller及Service注入运行时,依赖的对象(在成员变量上添加@Autowired注解)
运行测试
要把某个对象交给IOC容器管理,需要在对应的类上加上如下直接之一:
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,使用较少) |
准备工作(创建spring boot工程,数据库表user,实体类User)
引入MyBatis的相关依赖,配置MyBatis(数据库连接信息)(连接自己的数据库和密码)
#驱动类名称
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/db1
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456
编写SQL语句(注解/XML)
@Mapper //在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {
//查询全部用户信息
@Select("select * from user")
public List<User> list();
}
单元测试
@SpringBootTest
class SpringbootMybatisQuickstartApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testListUser(){
List<User> userList = userMapper.list();
userList.stream().forEach(user -> {
System.out.println(user);
});
}
}
默认在mybatis中编写SQL语句是不识别的,可以做如下配置:
右键->Show Context Actions->Inject Language or reference ->MySQL
数据库名称爆红:
数据库连接池优势:
标准接口:DataSource
官方(sun)提供的数据库连接池接口,由第三方组织实现此接口
功能:获取连接
Connection getConnection() throws SQLException;
常见产品:
官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
<dependency>
<groupld>com.alibabagroupld>
<artifactld>druid-spring-boot-starterartifactld>
<version>1.2.8version>
dependency>
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/数据库名
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.5version>
<relativePath/>
parent>
注解 | 作用 |
---|---|
@Getter/@Setter | 为所有属性提供get/set方法 |
@ToString | 会给类自动生成易阅读的toString方法 |
@EqualsAndHashCode | 根据类所拥有的非静态字段重写equals方法和hashcode方法 |
@Data | 提供了更综合的生成代码功能(@Getter+@Setter+@ToString+@EqualsAndHashCode) |
@NoArgsConstructor | 为实体类生成无参的构造器方法 |
@AllArgsConstructor | 为实体类生成除了static修饰的字段之外带有各参数的构造方法 |
Lombok的依赖
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
lombok会在编译时,自动生成对应的Java代码,我们使用Lombok时,还需要安装一个Lombok的插件(Java自带)
1.准备数据库表
create table user(
id int unsigned primary key auto_increment comment 'ID',
name varchar(100) comment '姓名',
age tinyint unsigned comment '年龄',
gender tinyint unsigned comment '性别, 1:男, 2:女',
phone varchar(11) comment '手机号'
) comment '用户表';
insert into user(id, name, age, gender, phone) VALUES (null,'白眉鹰王',55,'1','18800000000');
insert into user(id, name, age, gender, phone) VALUES (null,'金毛狮王',45,'1','18800000001');
insert into user(id, name, age, gender, phone) VALUES (null,'青翼蝠王',38,'1','18800000002');
insert into user(id, name, age, gender, phone) VALUES (null,'紫衫龙王',42,'2','18800000003');
insert into user(id, name, age, gender, phone) VALUES (null,'光明左使',37,'1','18800000004');
insert into user(id, name, age, gender, phone) VALUES (null,'光明右使',48,'1','18800000005');
select * from user;
-- 部门管理
create table dept(
id int unsigned primary key auto_increment comment '主键ID',
name varchar(10) not null unique comment '部门名称',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '部门表';
insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now());
-- 员工管理
create table emp (
id int unsigned primary key auto_increment comment 'ID',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
image varchar(300) comment '图像',
job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
entrydate date comment '入职时间',
dept_id int unsigned comment '部门ID',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';
INSERT INTO emp(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
(11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()),
(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()),
(13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()),
(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2010-01-01',2,now(),now()),
(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());
-- 根据ID删除数据
delete from emp where id = 17;
-- 登录
select count(*) from emp where username = 'zhangwuji' and password = '123456';
select count(*) from emp where username = 'zhangwuji' and password = '111';
select count(*) from emp where username = 'wuieuwiueiwuiew' and password = '' or '1' = '1';
3.application.properties中引入数据库连接信息:
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/emp
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456
4.创建对应的实体类 Emp(实体类属性采用驼峰命名):
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer id;
private String username;
private String password;
private String name;
private String gender;
private String image;
private Short job;
private LocalDate entrydata;
private Integer deptId;
private LocalDateTime createTime;
private LocalDateTime updatetime;
}
5.准备Mapper接口 EmpMapper:
package com.example.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmpMapper {
}
SQL语句:
delete from emp where id = 17;
接口方法:
@Mapper
public interface EmpMapper {
//根据ID删除数据
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
}
如果mapper接口方法形参只有一个普通类型的参数,#{…}里面的属性名可以随便写,如:#{id},#{value}
优势:
#{…}
${…}
SQL语句:
-- 插入
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) VALUE ('Tom','汤姆','1','1.jpg',1,'2005-01-01',1,now(),now())
接口方法:
//新增员工
@Insert("insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
" values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
public void insert(Emp emp);
在测试类中实现:
@Test
public void testInsert(){
//构造员工对象
Emp emp = new Emp();
emp.setUsername("Tom");
emp.setName("汤姆");
emp.setImage("1.jpg");
emp.setGender(String.valueOf((short)1));
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,22));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
//执行新增员工信息操作
empMapper.insert(emp);
}
如何实现在插入数据之后返回所插入行的主键值?
主键返回代码实现:
@Mapper
public interface EmpMapper {
//会自动将生成的主键值,赋值给emp对象的id属性
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
}
SQL语句:
-- 更新员工
update emp set username = '',name='',gender='',image='',
job='',entrydate='',dept_id='',update_time='' where id=1;
接口方法:
@Update("update emp set username =#{username},name=#{name},gender=#{gender},image=#{image}," +
" job=#{job},entrydate=#{entrydate},dept_id=#{deptId},update_time=#{updateTime} where id= #{id} ;")
public void update(Emp emp);
在测试类中实现:
//更新员工
@Test
public void testUpdate(){
//构造员工对象
Emp emp = new Emp();
emp.setId(18);
emp.setUsername("Tom1");
emp.setName("汤姆1");
emp.setImage("1.jpg");
emp.setGender(String.valueOf((short)1));
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
//执行更新员工操作
empMapper.update(emp);
}
SQL语句:
select * from emp where id = 18;
接口方法:
//根据ID查询员工
@Select("select * from emp where id = #{id}")
public Emp testGetById(Integer id);
在测试类中实现:
//根据ID查询员工
@Test
public void testGetById(){
Emp emp = empMapper.testGetById(18);
System.out.println(emp);
}
解决方案:
方案一:给字段起别名,让别名与实体类属性一致
@Select("select id,username,password,name,gender,image,job,entrydate," +
"dept_id deptId,create_time createTime,update_time updateTime from emp where id = #{id}")
public Emp getById(Integer id);
方案二:通过@Results,@Result注解手动映射封装
@Results({
@Result(column = "dept_id", property = "deptId"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
方案三:开启mybatis的驼峰命名自动映射开关 —a_cloumn ----->aCloumn
//根据ID查询员工
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
在application.properties中添加:
#开启mybatis的驼峰命名自动映射开关 ---a_cloumn ----->aCloumn
mybatis.configuration.map-underscore-to-camel-case=true
SQL语句:
-- 条件查询员工
select * from emp where name like '%张%' and gender = 1 and entrydate between '2010-01-01' and '2020-01-01' order by update_time desc ;
接口方法:
//条件查询员工
@Select("select * from emp where name like '%${name}%' and gender =#{gender} and " +
"entrydate between #{begin} and #{end} order by update_time desc ")
public List<Emp> list(String name, Short gender, LocalDate begin,LocalDate end);
在上述接口方法中,使用了${name},则生成的不是预编译的SQL,会有性能低,不安全,存在SQL注入问题。
解决方法:使用concat()字符串拼接函数
-- concat 字符串拼接函数
select * from emp where name like concat('%',?,'%') and gender = 1 and entrydate between '2010-01-01' and '2020-01-01' order by update_time desc ;
@Select("select * from emp where name like concat('%',#{name},'%') and gender =#{gender} and " +
"entrydate between #{begin} and #{end} order by update_time desc ")
public List<Emp> list(String name, Short gender, LocalDate begin,LocalDate end);
1.规范:
2.MybatisX:一款基于IDEA的快速开发Mybatis的插件,提高效率。
3.关于选择Mybatis注解或映射:
使用Mybatis的注解,主要是来完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
官方说明:https://mybatis.net.cn/getting-started.html
4.约束(可以直接去官网入门查询)
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
:用于判断条件是否成立,使用test属性进行条件判断,如果条件为true,则拼接SQL。
:where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的and或or。
:动态地在行首插入SET关键字,并会删除额外的逗号。(用于update语句中)
动态SQL语句:
<select id="list" resultType="com.itheima.pojo.Emp">
select * from emp
where
<if test="name != null">
name like concat('%',#{name},'%')
if>
<if test="gender != null">
and gender = #{gender}
if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
if>
order by update_time desc
select>
测试方法:
@Test
public void testList(){
//性别数据为null、开始时间和结束时间也为null
List<Emp> list = empMapper.list("张", null, null, null);
for(Emp emp : list){
System.out.println(emp);
}
}
使用
标签代替SQL语句中的where关键字:
<select id="list" resultType="com.example.pojo.Emp">
select *
from emp
<where>
<if test="name != null">
name like concat('%',#{name},'%')
if>
<if test="gender != null">
and gender =#{gender}
if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
if>
order by update_time desc
where>
select>
修改Mapper接口:
@Mapper
public interface EmpMapper {
//删除@Update注解编写的SQL语句
//update操作的SQL语句编写在Mapper映射文件中
public void update(Emp emp);
}
修改Mapper映射文件:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.EmpMapper">
<update id="update2">
update emp
set
<if test="username != null">username =#{username},if>
<if test="name != null">name =#{name},if>
<if test="gender != null">gender =#{gender},if>
<if test="image != null">image =#{image},if>
<if test="job != null">job =#{job},if>
<if test="entrydate != null">entrydate =#{entrydate},if>
<if test="deptId != null">dept_id =#{deptId},if>
<if test="updateTime != null">update_time =#{updateTime}if>
where id = #{id};
update>
mapper>
测试方法:
@Test
public void testUpdate2(){
//要修改的员工信息
Emp emp = new Emp();
emp.setId(18);
emp.setUsername("Tom111");
emp.setName("汤姆111");
emp.setUpdateTime(LocalDateTime.now());
//调用方法,修改员工数据
empMapper.update(emp);
}
使用
标签代替SQL语句中的set关键字:
update emp
username =#{username},
name =#{name},
gender =#{gender},
image =#{image},
job =#{job},
entrydate =#{entrydate},
dept_id =#{deptId},
update_time =#{updateTime}
where id = #{id};
SQL语句:
delete from emp where id in(18,19);
接口方法:
//批量删除员工
public void deleteByIds(List<Integer> ids);
XML映射文件:
<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
delete>
可以对重复的代码片段进行抽取,将其通过
标签封装到一个SQL片段,然后再通过
标签进行引用。
:定义可重用的SQL片段
:通过属性refid,指定包含的sql片段SQL片段: 抽取重复的代码
<sql id="commonSelect">
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
sql>
然后通过
标签在原来抽取的地方进行引用。操作如下:
<select id="list" resultType="com.itheima.pojo.Emp">
<include refid="commonSelect"/>
<where>
<if test="name != null">
name like concat('%',#{name},'%')
if>
<if test="gender != null">
and gender = #{gender}
if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
if>
where>
order by update_time desc
select>
传统URL风格如下:
http://localhost:8080/user/getById?id=1 GET:查询id为1的用户
http://localhost:8080/user/saveUser POST:新增用户
http://localhost:8080/user/updateUser POST:修改用户
http://localhost:8080/user/deleteUser?id=1 GET:删除id为1的用户
原始的传统URL,定义比较复杂,而且将资源的访问行为将会对外暴露出来
基于REST风格URL如下:
http://localhost:8080/users/1 GET:查询id为1的用户
http://localhost:8080/users POST:新增用户
http://localhost:8080/users PUT:修改用户
http://localhost:8080/users/1 DELETE:删除id为1的用户
在REST风格的URL中,通过四种请求方式,来操作数据的增删改查。
基于REST风格,定义URL,URL将会更加简洁、更加规范、更加优雅。
注意事项:
查看页面原型明确需求 --> 阅读接口文档 --> 思路分析 --> 接口开发 --> 接口测试 --> 前后端联调
在调用工具类后,可以将参数配置在配置文件中
application.properties是springboot项目默认的配置文件,所以springboot程序在启动时会默认读取application.properties配置文件,可以使用一个现成的注解:@Value,获取配置文件中的数据。
@Value 注解通常用于外部配置的属性注入,具体用法为: @Value(“${配置文件中的key}”)
#数据库连接信息
spring:
profiles:
active: dev
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tlias
username: root
password: 123456
#文件上传的配置
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
#mybatis配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
#开启事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
SringBoot提供了多种属性配置方式:
常见配置文件格式对比
XML:臃肿
properties:层级结构不清晰
yml格式的数据有以下特点:
yml基本语法:
当把application.properties换成application.yml,出现报错信息:No active profile set, falling back to 1 default profile: "default"时,
出现错误的原因为:
properties和yml的语法格式不一样,一般情况下,yml文件要指明是生产环境还是开发环境
(dev:开发环境 prod:生产环境 test:测试环境)
解决方法如下:
方法一:在Configurations中添加--spring.profiles.active=dev
方法二:在配置文件中添加
可以直接将配置文件中配置项的值自动的注入到对象的属性中
实现过程:
需要创建一个实现类,且实体类中的属性名和配置文件当中key的名字必须要一致
比如:配置文件当中叫endpoints,实体类当中的属性也得叫endpoints,另外实体类当中的属性还需要提供 getter / setter方法
需要将实体类交给Spring的IOC容器管理,成为IOC容器当中的bean对象
在实体类上添加@ConfigurationProperties
注解,并通过perfect属性来指定配置参数项的前缀
@ConfigurationProperties与@Value
相同点:
不同点:
如果要注入的属性非常的多,并且还想做到复用,就可以定义这么一个bean对象。通过 configuration properties 批量的将外部的属性配置直接注入到 bin 对象的属性当中。在其他的类当中,要想获取到注入进来的属性,直接注入 bin 对象,然后调用 get 方法,就可以获取到对应的属性值了
事务回滚:
事务回滚是指将该事务已经完成对数据库的更新操作撤销,在事务中,每个正确的原子都会被顺序执行,知道遇到错误的原子操作。
回滚:
回滚是删除由一个或多个部分完成的事务执行的更新,为保证应用程序、数据库或系统错误后还原数据库的完整性,需要使用回滚。
回滚包括程序回滚和数据回滚等类型(泛指程序更新失败,返回上一次正确状态行为)
@Transactional注解书写位置:
在配置文件中添加:
#开启事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
rollbackFor
propagation
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
… |
REQUIRED :大部分情况下都是用该传播行为即可。
REQUIRES_NEW :当不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
AOP面向切面编程和OOP面向对象编程一样,它们都仅仅是一种编程思想,而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)。
AOP优点:
导入依赖:在pom.xml中导入AOP的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
- @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
- @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
执行顺序:
不同切面类中,默认按照切面类的类名字母排序:
用@Order(数字)加在切面类上来控制顺序
目标方法前的通知方法:数字小的先执行
目标方法后的通知方法:数字小的后执行
execution主要根据方法的返回值,包名宁,类名,方法名,方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throw 异常)
其中带?的表示可以省略的部分
可以使用通配符描述切入点
*
:单个独立的任意符号,可以通配任意返回值,包名,类名,方法名,任意类型的一个参数,也可以通配包,类,方法名的一部分
execution(* com.* .service.*.update*(*))
..
多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
execution(* com.dream..DeptService.*(..))
可以使用 且(&&)、或(||)、非(!)来组合比较复杂的切入点表达式
@annotation切入点表达式,用于匹配标识有特定注解的方法
编写自定义注解
在业务类要做为连接点的方法上添加自定义注解
execution切入点表达式
annotation 切入点表达式
将案例中增、删、改相关接口的操作日志记录到数据库表中
操作日志信息包含:
所记录的日志信息包括当前接口的操作人是谁操作的,什么时间点操作的,以及访问的是哪个类当中的哪个方法,在访问这个方法的时候传入进来的参数是什么,访问这个方法最终拿到的返回值是什么,以及整个接口方法的运行时长是多长时间。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
-- 操作日志表
create table operate_log(
id int unsigned primary key auto_increment comment 'ID',
operate_user int unsigned comment '操作人',
operate_time datetime comment '操作时间',
class_name varchar(100) comment '操作的类名',
method_name varchar(100) comment '操作的方法名',
method_params varchar(1000) comment '方法参数',
return_value varchar(2000) comment '返回值',
cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
//操作日志实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //主键ID
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Long costTime; //操作耗时
}
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Target(ElementType.METHOD) //作用在方法上
public @interface Log {
}
@Slf4j
@Component
@Aspect //切面类
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.example.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable{
//操作人ID -当前登陆员工ID
//获取请求头中的jwt令牌,解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().getName();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
long begin = System.currentTimeMillis();
//调用原始目标方法运行
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
// 操作耗时
Long costTime = end - begin;
//方法返回值
String returnValue = JSONObject.toJSONString(result);
//记录日志
OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
operateLogMapper.insert(operateLog);
log.info("AOP记录操作日志:{}",operateLog);
return result;
}
}
获取request对象,从请求头中获取到jwt令牌,解析令牌获取出当前用户的id
SpringBoot中支持三种格式的配置文件:
三种配置文件的优先级(从高到低)为:
虽然Springboot支持多种格式的配置文件,但是在项目开发时,推荐使用统一的一种格式的配置(yml)
在SpringBoot项目当中除了以上3种配置文件外,SpringBoot为了增强程序的扩展性,除了支持配置文件的配置方式以外,还支持另外两种常见的配置方式:
Java系统属性
-Dserver.port=9000
命令行参数
--server.port=10010
优先级(从低到高):
1、Java面向对象,对象有方法和属性,那么就需要对象实例来调用方法和属性(即实例化);
2、凡是有方法或属性的类都需要实例化,这样才能具象化去使用这些方法和属性;
3、规律:凡是子类及带有方法或属性的类都要加上注册Bean到Spring IoC的注解;
4、Bean通过反射、代理来实现,能代表类所拥有的东西;
5、在Spring中,标识一个@符号,那么Spring就会从这里拿到一个Bean或者给出一个Bean
二、注解分为两类:
1、一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;
2、一类是注册Bean,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。
总结:
1、凡是子类及带属性、方法的类都注册Bean到Spring中,交给它管理;
2、@Bean 用在方法上,告诉Spring容器,可以从下面这个方法中拿到一个Bean
默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中,如果想要主动获取这些bean,可以通过以下三种方式:
根据name获取bean:
Object getBean(String name)
根据类型获取bean
<T> T getBean(Class<T> requiredType)
根据name获取bean(带类型转换)
<T> T getBean(String name, Class<T> requiredType)
作用域 | 说明 |
---|---|
singleton | 容器内同名称的bean只有一个实例(单例)(默认) |
prototype | 每次使用该bean时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web环境中) |
session | 每个会话范围内会创建新的实例(web环境中) |
application | 每个应用范围内会创建新的实例(web环境中) |
可以借助Spring中的@Scope注解来进行配置作用域:
@Scope("prototype")
注意事项:
- 如果通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就算方法名
- 如果第三方bean需要依赖其他bean对象,直接在bena定义方法中设置形参即可,容器会根据类型字段装配
@Component及衍生注解与@Bean注解使用场景?
- 项目中定义的,使用@Component及其衍生注解
- 项目中引入第三方的,使用@Bean注解
使用SpringBoot,不需要像繁琐的引入依赖,只需要引入一个依赖就可以了,那就是web开发的起步依赖:springboot-starter-web
Maven的依赖传递
在SpringBoot给我们提供的这些起步依赖当中,已提供了当前程序开发所需要的所有的常见依赖(官网地址:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#using.build-systems.starters)。
比如:springboot-starter-web,这是web开发的起步依赖,在web开发的起步依赖当中,就集成了web开发中常见的依赖:json、web、webmvc、tomcat等。我们只需要引入这一个起步依赖,其他的依赖都会自动的通过Maven的依赖传递进来。
起步依赖的原理就是Maven的依赖传递
引入进来的第三方依赖当中的bean以及配置类为什么没有生效?
解决问题:
@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要扫描的包
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
导入形式主要有以下几种:
导入普通类
导入配置类
导入ImportSelector接口实现类
使用第三方依赖提供的 @EnableXxxxx注解
关于第四种:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
public @interface EnableHeaderConfig {
}
@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是:
当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。
@Conditional注解:
web后端开发现在基本上都是基于标准的三层架构进行开发的,在三层架构当中,Controller控制器层负责接收请求响应数据,Service业务层负责具体的业务逻辑处理,而Dao数据访问层也叫持久层,就是用来处理数据访问操作的,来完成数据库当中数据的增删改查操作
如果在执行具体的业务处理之前,需要去做一些通用的业务处理,比如:要进行统一的登录校验,要进行统一的字符编码等这些操作时,就可以借助于Javaweb当中三大组件之一的过滤器Filter或者是Spring当中提供的拦截器Interceptor来实现
而为了实现三层架构层与层之间的解耦,学习了Spring框架当中的第一大核心:IOC控制反转与DI依赖注入
所谓控制反转,指的是将对象创建的控制权由应用程序自身交给外部容器,这个容器就是我们常说的IOC容器或Spring容器。
而DI依赖注入指的是容器为程序提供运行时所需要的资源。
Filter过滤器、Cookie、 Session这些都是传统的JavaWeb提供的技术。
JWT令牌、阿里云OSS对象存储服务,是现在企业项目中常见的一些解决方案。
IOC控制反转、DI依赖注入、AOP面向切面编程、事务管理、全局异常处理、拦截器等,这些技术都是 Spring Framework框架当中提供的核心功能。
Mybatis就是一个持久层的框架,是用来操作数据库的。
在Spring框架的生态中,对web程序开发提供了很好的支持,如:全局异常处理器、拦截器这些都是Spring框架中web开发模块所提供的功能,而Spring框架的web开发模块,也称为:SpringMVC
SSM,就是由:SpringMVC、Spring Framework、Mybatis三块组成。
基于传统的SSM框架进行整合开发项目会比较繁琐,而且效率也比较低,所以在现在的企业项目开发当中,基本上都是直接基于SpringBoot整合SSM进行项目开发的。
…
jar:普通模块打包,springboot项目基本都是jar包(内嵌tomcat运行)
war:普通web程序打包,需要部署在外部的tomcat服务器中运行
pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
继承关系实现:
pom
(默认jar
)注意:
- 在子工程中,配置了继承关系之后,坐标中的groupId是可以是可以省略的,因为会自动继承父工程的
- relativePath指定父工程的pom文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)
- 若父子工程都配置了同一个依赖的不同版本,以子工程的为准
来统一管理依赖版本
版本号,父工程统一管理,变更版本依赖,只需在父工程中统一变更
与
的区别:
是直接依赖,在父工程配置了依赖,子工程会直接继承下来。
是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
聚合:将多个模块组织成一个整体,同时进行项目的构建
聚合工程:一个不具有业务功能的“空”工程(有且仅有一个pom文件)
作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
maven中可以通过
设置当前聚合工程所包含的子模块名称
聚合工程所包含的模块,在构建时,会自动根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关
作用
聚合用于快速构建项目
继承用于简化依赖配置、统一管理依赖
相同点:
聚合与继承的pom.xml文件打包方式均为pom,通常将两种关系制作到同一个pom文件中
聚合与继承均属于设计型模块,并无实际的模块内容
不同点:
聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
第一步配置:在maven的配置文件中配置访问私服的用户名、密码。
第二步配置:在maven的配置文件中配置连接私服的地址(url地址)。
第三步配置:在项目的pom.xml文件中配置上传资源的位置(url地址)。
配置好了上述三步之后,要上传资源到私服仓库,就执行执行maven生命周期:deploy。
私服仓库说明:
- RELEASE:存储自己开发的RELEASE发布版本的资源。
- SNAPSHOT:存储自己开发的SNAPSHOT发布版本的资源。
- Central:存储的是从中央仓库下载下来的依赖。
项目版本说明:
- RELEASE(发布版本):功能趋于稳定、当前更新停止,可以用于发行的版本,存储在私服中的RELEASE仓库中。
- SNAPSHOT(快照版本):功能不稳定、尚处于开发中的版本,即快照版本,存储在私服的SNAPSHOT仓库中。
具体操作:
1.设置私服的访问用户名/密码(在自己maven安装目录下的conf/settings.xml中的servers中配置)
2.设置私服依赖下载的仓库组地址(在自己maven安装目录下的conf/settings.xml中的mirrors、profiles中配置)
3.IDEA的maven工程的pom文件中配置上传(发布)地址(直接在tlias-parent中配置发布地址)
配置完成之后,我们就可以在tlias-parent中执行deploy生命周期,将项目发布到私服仓库中。