学校选修的课程的期末考试
总体包括JPA,Spring,SpringMVC,Vue,编程题目。下面是详写。
JPA
- JDBC:Java Data Base Connectivity,提供了一组Java程序访问关系数据库的API,使Java程序可以与任何支持SQL标准的数据库交互。
- ORM:Object Relational Mapping,对象关系映射是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
- JPA:Java Persistence API,Java持久化API提供了一套对象/关系映射工具,通过操作实体对象,管理程序的关系型数据。
- spring-data:将第三方ORM框架封装到spring-data,实现基于Spring项目开发的一致性体验;从而避免与各框架在整合/升级/配置等等带来的问题。
- Spring-data-jpa:封装了基于JPA规范的Hibernate框架,提供了更简洁的开发接口,包括通用的持久化接口和查询注解。
- JPQL:The Java Persistence Query Language,数据库无关的,基于实体类的,面向对象的查询语句,通过操作类、对象、属性等获得数据结果集。
1.ORM技术的作用与意义
Object Relational Mapping
对象关系映射是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术
直接使用JDBC太过复杂,容易出错,不易于编码,扩展和修改。对象映射可以使业务逻辑和数据库分离,使得数据库透明,开发人员真正的面向对象。
ORM的优点:
- 业务逻辑访问、操作对象而非数据库记录
- 无需关心底层数据库,通过面向对象逻辑影藏具体SQL实现细节
- 实体基于业务关系而非数据库表结构
- 适应应用的快速开发与迭代
2. 基本ORM映射注解
@Entity
:修饰声明实体类
springboot启动时,自动扫描全部实体类
每个实体类映射为一张数据表,实体类可通过全限定类名(包)区分,但映射的数据表仅能通过名称区分,实体类名称不能重复
实体类映射为一张数据表,
类中每一个属性对应数据库中的一个字段。
实体类的一个对象映射为数据库表中的一条记录@Id
:每个实体必须包含的唯一的对象标识符@GeneratedValue
,声明是自动生成的值,strategy声明主键生成策略
JPA提供四种主键生成策略:
生成策略 | 描述 |
---|---|
GenerationType.IDENTITY | 自增长主键,多数数据库支持此策略(MySQL, SQL Server, PostgreSQL) |
GenerationType.SEQUENCE | 序列(Oracle) |
GenerationType.TABLE | 不依赖于数据库的具体实现,通过创建序列表维护表的主键,便于移植。 |
GenerationType.Auto | 默认值。由JPA服务商选择生成策略,无法对具体数据库进行优化 |
@Column
- nullable:指定该列是否允许为空,布尔型,默认值为true
- Length:指定该列保存数据的最大长度,默认长度225
- Unique:指定该列是否具有唯一约束,默认为false
- columnDefinition:生成列时使用的SQL片段
@Table
name:对应关系型数据库的数据表名,默认为实体类名,当希望实体类名称与数据表名称不同,或名称与数据库关键字冲突时使用@OneToMany
One端使用@OneToMany声明与Many端关系,声明mappedBy属性放弃关系的维护,声明在另一端映射的属性名称
One端放弃关系的维护,用mappedby声明在many端的名称,one端是个集合,many是单一元素。@ManyToOne
Many端使用@ManyToOne声明与One端关系@ManyToMany
//实体类代码
@Data //lombok注解
@NoArgsConstructor //lombok注解
@Entity
public class User {
@Id
@GeneratedValue
@Column(length = 16) //声明长度为16 使用uuid进行保存 长度16即可
private UUID id;
private String name;
private LocalDate birthday;
//对行进行描述,定义为插入时的时间戳
@Column(columnDefinition = "timestamp default current_timestamp",
insertable = false,
updatable = false)
private LocalDateTime insertTime;
//同上定义,当修改时 更改记录值
@Column(columnDefinition = "timestamp default current_timestamp on update current_timestamp",
insertable = false,
updatable = false)
private LocalDateTime updateTime;
}
3. 延迟加载策略
Fetch
- 默认,当查询加载对象时,除关联属性外全部直接加载。整型、字符串、日期时间等等
- 默认对one端,即时加载,即,在查询出对象后,对象中包含的one端关联属性,同时查询加载。(当加载many时,把内部的one同时也加载了)
- 默认对many端,延迟加载,即,在查询对象后,对象中包含的many端关联属性,只有在事务内真正使用时才加载(执行相关select语句),事务关闭后视图加载将导致异常。
//强制声明即时加载
@OneToMany(mappedBy = "user",fetch = FetchType.EAGER)
private List<Address06> addresses;
//FetchType.EAGER 即时加载
//FetchType.LAZY 延迟加载
Cascade
cascade:指定对级联对象执行的级联操作策略
CascadeType.MERGE
CascadeType.PERSIST
CascadeType.REFRESH
CascadeType.REMOVE
CascadeType.ALL
级联操作与关联关系维护无关,即,即使设置PERSIST级联也无法完成从关系被维护端建立与关系维护端的关系
4. JPQL查询语言
Java Perisistence Query Language:数据库无关的,基于实体的,面向对象的查询语句,通过操作底层类,对象,属性等获得数据结果集
- 对SQL语句的封装,底层仍然是sql语句,由jpql解析器完成向sql语句的转换
- 内置函数,字符串处理函数,数学函数,时间函数等等
- 支持使用原生SQL语句
- 关键字不区分大小写,建议全部大写
- 包,类,属性等区分大小写
- 别名不允许使用关键字
当需要批量操作时,查询出大量对象再执行更新操作影响性能,可通过直接执行更新语句实现。
@Modifying
,支持执行基于JPQL的更新/删除等持久化操作,而非查询语句
--jpql语句
-- 返回User实体类的全部对象
SELECT u FROM User u;
SELECT u.id,u.name FROM hibernate.user u;--sql语句
-- 表中的列名 表名
SELECT u.id u.name FROM User u;--jpql语句
-- 实体类的属性名 实体名
5. 基本查询语句
--Count 计数
SELECT COUNT(u.name) FROM User u;
--属性过滤
SELECT u.name FROM User u WHERE u.id = 1;
--字符串过滤
FROM User u WHERE u.name = 'BO';
--Between
FROM User u WHERE u.age between 18 and 30;
--Order by 基于关联集合对象个数
FROM User u ORDER BY SIZE(u.addresses);
FROM User u ORDER BY SIZE(u.addresses) DESC;
6. 关联对象的隐式查询方法
- 显式,使用join关键字
inner join
left outer join
right outer join
full join(不推荐) - 隐式,使用“点”应用关联属性对象,自动生成合适的SQL语句
FROM Address a WHERE a.user.id = 1
a.user.id => 实体对象.关联对象属性名称.属性名称
错误例子!!!
SELECT detail FROM Address a;--格式必须为“实体.属性”
SELECT a.Detail FROM Address a;--JPQL语句,类、属性区分大小写
SELECT u.addresses.detail FROM User u WHERE u.id = 1;
--addresses属性为了封装Address实体对象的集合类型 但不是Address实体对象
example:
查询原则,从预获取的属性对象所在实体查询(from),从该实体的关联属性将查询参数引入
--姓名为BO的用户的所有地址?
FROM Address a WHERE a.user.name='BO';
SELECT u.addresses FROM User u WHERE u.name = 'BO';
--地址为925的用户?
SELECT a.user FROM Address a WHERE a.detail ='925';
7. 数据库事务的作用及意义
数据库事务,是指作为单个逻辑工作单元执行的一列操作。一个逻辑工作单元要成为事务,必须要满足ACID
- 原子性:事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全部不执行
- 一致性:事务保证数据库的状态从一个一致性的状态转变到另一个一致性状态(DB中的数据应满足完整性约束)
- 隔离性:由并发事务所作的修改必须与任何其他并发事务所做的修改隔离
- 持久性:事务完成后,它对于系统的修改是永久性的。
数据库事务并发操作带来的数据不一致现象:
- 幻读:一个事务执行2次查询,记录结果数不一致
- 赃读:一个事务读取到另一个事务修改但未提交的更新的数据
- 重复度:一个事务2次读取同一数据得到不同结果
锁:
- 乐观锁实现:能够同时保证高并发和高可伸缩性
应用级别的版本检查 - 悲观锁
8. 基本spring-data-jpa的使用方法
Spring-Data-JPA Online Tutorials: https://docs.spring.io/spring-data/jpa/docs/
即从原SpringMVC+Spring+第三方ORM框架(JPA/hibernate等),将第三方ORM框架封装到spring-data,实现基于spring项目开发的一致性体验;从而避免和各框架在整合、升级、配置等等带来的问题
ex:Spring-data-MongoDB,Spring-data-Redis,Spring-data-JPA
spring-data-jpa -> spring-data ->JPA -> Hibernate -> JDBC
Spring-data-jpa,简化了基于JPA/Hibernate的开发,与spring容器无缝整合,其封装了基于JPA规范的Hibernate框架,提供了更简洁的开发接口,包括
- 通用的持久化接口
- 查询注解
CrudRepository<T,ID>
,各ORM框架通用的CRUD接口(意义类似抽象的协议无关的Servlet接口)
JpaRepository<T,ID>
,继承以上接口的具体JPA接口(意义类似具体协议的HttpServlet接口)
JpaRepository
接口,封装了改变实体对象状态的JPA方法,简化了基于实体对象的CRUD操作(无需关心较复杂实体对象状态)
为每一个实体类,创建继承了JpaRepository
接口的操作接口
Spring-data-jpa基于JDK动态代理,自动创建接口实现类完成操作,无需手动编写接口实现类(类似MyBatis)
编写测试实体类,创建对应的repository接口
@Repository
public interface User05Repository extends BaseRepository<User05, Integer> {
}
JpaRepository接口中的常用方法(包含继承接口),
将该对象置于缓存(持久化上下文)。事务结束后将对象同步到数据库
T save(T entity)
List<S> saveAll(Iterable<S> entities)
void flush()
S saveAndFlush(S entity)
同步的结果
对象没有主键,相当于添加
对象有主键,相当于更新
基于对象/主键删除。必须包含主键,将对象置于缓存,同步
void delete(T entity)
void deleteAll()
void deleteAll(Iterable<S> entities)
void deleteById(ID id)
基本查询,返回的对象置于缓存。复杂查询后期讨论
List<T> findAll()
Optional<T> findById(ID id)
long count(),获取全部记录数量。等其他方法
测试添加;添加,更新属性,执行save()方法,打输出,输出顺序查看
更新,仅保留主键,更新结果
添加,同一事务中查看数据库生成数据
@SpringBootTest
@Slf4j
@Transactional //开启支持事务
@Rollback(value = false)//关闭事务回滚
public class User05RepositoryTest {
@Autowired
private User05Repository user05Repository;
@Autowired
EntityManager manager;
@Test
public void test_addUser() {
User05 user05 = new User05();//新建一个对象
user05.setName("a");//设置该对象的name属性值
user05Repository.save(user05);//用来表示对该对象的保存 ,如果接下来改变了该值,也会执行相应update操作
user05.setName("b");//改变属性的值
/*
执行完该test方法后,数据库中的记录是 名字为b的一个用户
执行流程:
Hibernate: insert into user05 (name) values (?)
Hibernate: select last_insert_id()
Hibernate: update user05 set name=? where id=?
*/
/*
如果顺序如果如此 则仅会执行一次insert操作,没有update操作
User05 user05 = new User05();
user05.setName("a");
user05.setName("b");
user05Repository.save(user05);
-----------------------------
Hibernate: insert into user05 (name) values (?)
*/
}
@Test
public void test_addUser2() {
User05 user05 = user05Repository.findById(4).orElse(null);
log.debug("{}", user05.getInsertTime());
}
@Test
public void test_addUser3() {
User05 user05 = new User05();
user05.setId(2);
user05Repository.save(user05);
//如果没有该id 则会insert 有时则会比较对象,如果不同则会进行update 否则无操作
}
@Test
public void test_addUser4() {
User05 user05 = new User05();
user05.setName("BO");
User05 u = user05Repository.save(user05);
log.debug("{}", u.getInsertTime());//此时结果会为空
/*
会发现 此时日志答应的对象inserttime为空,因为此时的对象还不是保存在数据库内后读取出的对象
Hibernate: insert into user05 (name) values (?)
2020-06-15 19:29:17 DEBUG com.example.springbootjpaexamples.example05.baserepository.User05RepositoryTest.test_addUser4[47] - null
*/
}
@Test
public void test_refresh() {
User05 user05 = new User05();
user05.setName("SUN");;//执行该方法相当于能把对象在数据库的主键拿到 只有id 没有inserttime
manager.persist(user05);
user05.setName("BO"); //此时的BO只在持久化上下文中,并没有在数据库中
manager.refresh(user05);//强制把数据库的数据同步过来 此时的inserttime就有了 SUN又被同步回来了 (这是实体管理器EntityManager的方法 不是spring-data-JPA提供的)
log.debug("{}", user05.getName()); //此时打印出的数据仍然为SUN
log.debug("{}", user05.getId());
log.debug("{}", user05.getInsertTime());
/*
Hibernate: insert into user05 (name) values (?)
Hibernate: select user05x0_.id as id1_10_0_, user05x0_.insert_time as insert_t2_10_0_, user05x0_.name as name3_10_0_ from user05 user05x0_ where user05x0_.id=?
2020-06-15 19:31:21 DEBUG com.example.springbootjpaexamples.example05.baserepository.User05RepositoryTest.test_refresh[57] - SUN
2020-06-15 19:31:21 DEBUG com.example.springbootjpaexamples.example05.baserepository.User05RepositoryTest.test_refresh[58] - 9
2020-06-15 19:31:21 DEBUG com.example.springbootjpaexamples.example05.baserepository.User05RepositoryTest.test_refresh[59] - 2020-06-15T19:31:21
*/
}
@Test
public void test_refresh2() {
User05 user05 = new User05();
user05.setName("SUN");
user05Repository.save(user05)
user05Repository.refresh(user05);
log.debug("{}", user05.getName());
log.debug("{}", user05.getId());
log.debug("{}", user05.getInsertTime());
}
}
因为spring-data-jpa自身没有添加refresh方法, 我们自己添加refresh方法的BaseRepository
@NoRepositoryBean
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
void refresh(T t);
}
BaseRespostoryImpl
public class BaseRespostoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
private EntityManager manager;//通过实体管理器
public BaseRespostoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
//此处IDEA 会报错 但实际上没有错误
super(entityInformation, entityManager);
this.manager = entityManager;
}
@Override
public void refresh(T t) {
manager.refresh(t);
}
}
入口文件
@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = BaseRespostoryImpl.class) //此处将BaseRespostoryImpl激活
public class SpringbootJpaExamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootJpaExamplesApplication.class, args);
}
}
Spring
1. 依赖注入设计思想
- IOC :控制反转,程序设计思想
- 控制:创建所需对象的控制权
- 反转:将主动、被动关系转换
- 控制反转:由程序主动创建所需对象,变为程序被动接受所需对象
- DI:依赖注入,组件由容器管理,在需要时由容器动态注入组件所需的其他组件
IoC == DI
IoC容器:可以实现依赖关系注入的容器,负责实例化,定位,配置应用程序所需对象,建立对象间的依赖关系,组装应用程序组件。
2. Bean的作用范围
Scope:Spring容器中bean相对应其他bean请求的可访问范围
- Singleton(default):容器中仅存在bean的一个实例。生命周期由容器管理;即容器对所有注入请求使用相同实例
- Prototype:容器不保存bean的实例,每次请求容器均创建一个新的实例
- Request
- Session
- Application
- Custom
- @Component,声明为组件
- @Repository,声明为持久化组件
- @Service ,声明为业务逻辑组件
- @Controller,声明为逻辑控制组件
- @Bean,方法级,声明方法返回的对象,由Spring容器管理。当需注入非自定义组件时
- @Configuration,声明为配置类组件
组件注解,仅用于区分组件功能
注解本身,不实现任何特殊功能
3. AOP的作用与意义
面向对象编程模式的扩展,一种程序设计思想,使调用者和被调用者之间解耦
单独学习Spring 时 需要显式声明AOP
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
面向对象、单一职责、内聚耦合等设计原则降低了应用的复杂性,提高了组件的可复用性和可维护性,但是,内聚到组件中的代码不可避免的具有重复性。
使调用者与被调用者之间解耦
4. 理解joinpoint、advice、pointcut、target object、AOP proxy、 weaving
- Joinpoint (连接点 )一个 预、添加、插入扩展操作的方法(希望在这个方法上进行一些添加和扩展,这就可以说这是一个连接点
- pointcut (切入点) 通过表达式 描述一组具有共同性的连接点。
对所有remove前缀方法的执行,均记录日志
对若有@Transaction注解方法的执行,均启动事务 - Advice(通知) 对单个连接点或者切入点(一组连接点) 采取的扩展操作行为。 例如,记录日志行为,事务行为,权限验证行为。
- Weaving(织入)将通知连接到连接点,创建代理对象的过程
- Target Object(目标对象) 连接点被织入通知后,生成的代理类创建的对象,Spring AOP通过运行时代理实现,因此必然是一个代理对象。
- Aspect (切面) 一个支持对横跨多个模块功能,实现通用统一扩展的行为。
例如,事务处理切面、日志管理切面、权限管理切面等。
5. 切入点表达式
切入点举例:
- service类下所有的类中方法:
execution(* com.ricemarch.springbootexperiment04.service..*.*(..))
- service包下的任何类中 以buy开头的方法:
execution(* com.ricemarch.springbootexperiment04..*.buy*(..))
- service包下的任何以vice结尾的类中 以buy开头的方法:
execution(* com.ricemarch.springbootexperiment04..*vice.buy*(..))
被切入的Service
@Component
public class AOPService03 {
public String hello(String name) {
return "welcome " + name;
}
}
MyAspect -切面
@Component
@Aspect
@Slf4j
public class MyAspect03 {
@Pointcut("execution(* com.example.springbootspringexamples.example03..*.*(..))")
//切入点表达式 表示切该包下所有类及其所有方法
public void pointcut() {}
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
log.debug(joinPoint.getTarget().getClass().getName());
for (Object a : joinPoint.getArgs()) {
log.debug("{}", a);
}
log.debug(joinPoint.getSignature().getName());
log.debug(joinPoint.getSignature().toString());
log.debug(joinPoint.getThis().getClass().getName());
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] objects = joinPoint.getArgs();
objects[0] = "SUN";
return joinPoint.proceed(objects);
}
}
测试类
@SpringBootTest
@Slf4j
public class AOPServiceTest {
@Autowired
private AOPService03 aopService03;
@Test
public void test_hello() {
log.debug(aopService03.hello("BO"));
}
}
测试结果
可以看到通过Around后,name已经被改为了SUN
6. 对被拦截对象的操作的接口
JoinPoint(org.aspectj.lang.JoinPoint)接口
,仅可以获取信息,无法改变连接点的执行- Object getTarget(),获取连接点对象
- Object[] getArgs(),获取方法参数
- Object getThis(),获取代理对象
- Signature getSignature(),获取方法签名
ProceedingJoinPoint接口
,继承JoinPoint,仅可注入around通知
只有around通知能修改结果- Object proceed() ,执行连接点方法,并返回执行结果,不执行则不会调用连接点方法
- Object proceed(Object[] args),基于指定参数执行连接点对象方法
7. 面向接口编程,面向切面编程,面向服务编程的意义
面向接口编程最直观的好处就是高内聚,低耦合
面向切面编程可以把类似日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离。可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码
SpringMVC
1. MVC的作用与意义
- 明确的角色,Clear separation of roles
- 适应性,非侵入性,灵活性(模型只返回处理结果,不关心结果的展示方式),Adaptability, non-intrusiveness, and flexibility.
- 可重用的业务代码(实现模型的复用),Reusable business code, no need for duplication.
- 可定制的绑定与校验,Customizable binding and validation.
- 可定制的映射处理与视图解析,Customizable handler mapping and view resolution.
- 灵活的模型转移,Flexible model transfer.
- 提供一个JSP/ JSP Form标签库,Spring tag library
SpringMVC工作流:
- 容器启动时,扫描到controller组件,注册controller请求处理方法配置到HandlerMapping
- Dispatcher(分发器)拦截请求
- Dispatcher调用HandlerMapping查找与请求必备的controller组件,并将请求转发给controller方法处理
- controller方法调用业务逻辑组件处理业务逻辑
- controller方法,序列化(Jackson json解析框架)请求所需的数据,返回。
2. 基本Controller方法的声明
@Controller
,声明为控制层组件。默认返回视图@RestController
,默认组件中全部方法返回序列化的json对象@RequestMapping
,请求映射,类型级方法级@GetMapping
;@PostMapping
;@PatchMapping
;@PutMapping
;@DeleteMapping
。
分别对应HTTP请求类型
@Slf4j
@RestController
@RequestMapping("/api/example01/")
public class ExampleController01 {
@GetMapping("index") //GET
public Map getIndex() {
return Map.of("name", "SUN");
}
@GetMapping("addresses")
public Map getAddresses() {
return Map.of("addresses", ADDRESSES);
}
@PostMapping("addresses") //POST
public Map postAddress(@RequestBody Address address) {
log.debug(address.getDetail());
log.debug(address.getComment());
return Map.of();
}
@PostMapping("addresses02")
public Map postAddress2(@RequestBody Address address) {
log.debug(address.getDetail());
log.debug(address.getComment());
log.debug("{}", address.getUser().getId());
return Map.of();
}
@GetMapping("addresses/{aid}")
public Map getAddress(@PathVariable int aid) {
Address address = ADDRESSES.stream()
.filter(a -> a.getId() == aid)
.findFirst()
.orElse(new Address());
return Map.of("address", address);
}
private final List<Address> ADDRESSES = create();
private List<Address> create() {
Address a1 = new Address(1, "956", "a", LocalDateTime.now());
Address a2 = new Address(2, "925", "b", LocalDateTime.now());
Address a3 = new Address(3, "121", "c", LocalDateTime.now());
return List.of(a1, a2, a3);
}
}
//三个等效的controller
@GetMapping("/hello")
@RequestMapping("/hello")
@RequestMapping(path="/hello",method= RequestMethod.GET)
3. 拦截器的声明使用方法
HandlerInterceptor
:拦截器
- preHandle
- postHandle 如果preHandle返回真,执行postHandle
- afterCompletion 不论成功失败后 都执行这个方法
@Component
@Slf4j
public class AdminInterceptor06 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("AdminInterceptor06");
return false;
}
}
WebMvcConfig 配置类,添加拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AdminInterceptor06 adminInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor)
.addPathPatterns("/api/example06/admin/**");
.//excludePathPatterns("/api/login"); 排除路径
}
}
4. RESTful风格,以及参数传递/获取的方法
核心思想:客户端发出的数据操作指令是“动词+宾语”(宾语意味着必须是名词)的结构,比如,GET/articles
动词通常就是五种 HTTP 方法,对应 CRUD 操作。
- GET:读取(Read)仅基于请求地址参数获得数据,无请求体
- POST:创建(Create)向请求地址提交数据,创建数据封装在请求体内
例如:基于正确的用户名密码,“创建”用户 - PUT:更新(Update)仅基于请求地址参数获得数据,无请求体
- PATCH:更新(Update),通常是部分更新,更新数据封装在请求体
- DELETE:删除(Delete),基于请求地址参数删除数据,无请求体
带请求体的请求类型(POST/PATCH),请求体数据类型必须为:application/json,即数据全部基于json格式封装。上传/下载等基于二进制数据
GET
/repos/{:owner}/{:repo}/issues/{:number}/comments
获取:全部仓库下/指定用户/指定仓库/全部议题下/指定议题/全部评论
/repos/{:owner}/{:repo}/issues/{:number}
获取:全部仓库下/指定用户/指定仓库/全部议题下/指定议题内容
POST
/repos/{:owner}/{:repo}/issues/{:number}/comments
向:全部仓库下/指定用户/指定仓库/全部议题下/指定议题/全部评论下
提交一个评论
PATCH
/repos/{:owner}/{:repo}/issues/comments/{:comment_id}
DELETE
/repos/{:owner}/{:repo}/issues/comments/{:comment_id}
例子:截取的一部分内容不要在意代码的正确性,仅用于说明使用方法
@RestController
@Slf4j
@RequestMapping("/api/teachers/")
public class TeacherController {
@GetMapping("/courses")
public Map listCourseByTeacherId() {
int teahcer_id = requestComponent.getUid();
//此处的requestCompoent是独立声明的从header里拿到值的组件,该组件的详细代码在下方
}
//@PathVariable,通过{}大括号声明地址参数
@GetMapping("courses/{course_id}")
public Map getCourseByCourseId(@PathVariable Integer course_id) {
Course course = teacherService.get(course_id);
return Map.of("message", "success", "data", course);
}
/*
在返回结果时,
Jackson默认序列化所有属性,无论值是否为null,
在springboot配置application.properties添加,
spring.jackson.default-property-inclusion=non_null,
序列化时忽略值为null的属性
*/
@PatchMapping("courses/setting")
public Map updateCourse(@Valid @RequestBody Course course) { /*...*/} //此处的Valid是对实体类的要求进行校验的
//@ApiOperation("修改指定id课程名称和学分")
@PatchMapping("courses/info")
public Map updateCourse2(@Valid @RequestBody Course course) { /*...*/} //此处的Valid是对实体类的要求进行校验的
@PostMapping("courses/{course_id}/students")
@Transactional //此处因为有数据的插入操作,所以需要事务注解
public void addStudents(@RequestBody List<StudentVO> students, @PathVariable Integer course_id) {/*...*/}
}
RequestComponent
@Component
@Slf4j
public class RequestComponent {
public int getUid() {//获取线程级的attribute
return (int) RequestContextHolder.currentRequestAttributes()
.getAttribute(MyToken.UID, RequestAttributes.SCOPE_REQUEST);
//MyToken.UID ,使用常量避免拼写错误带来的务必要的BUG,public static final String UID = "uid";
}
public User.Role getRole() {
return (User.Role) RequestContextHolder.currentRequestAttributes()
.getAttribute(MyToken.ROLE, RequestAttributes.SCOPE_REQUEST);
}
}
5. Controller全局统一异常处理的作用与声明方法
-
了解Http状态码及相关异常
-
如何建立统一的异常处理?使用
@RestControllerAdvice
@Slf4j
//声明捕获统一的异常处理
@RestControllerAdvice
public class ExceptionController {
/**
* 属性校验失败异常
*
* @param exception
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map handleValidException(MethodArgumentNotValidException exception) {
StringBuilder strBuilder = new StringBuilder();
exception.getBindingResult()
.getFieldErrors()
.forEach(e -> {
strBuilder.append(e.getField());
strBuilder.append(": ");
strBuilder.append(e.getDefaultMessage());
strBuilder.append("; ");
});
return Map.of("message", strBuilder.toString());
}
/**
* 请求类型转换失败异常
*
* @param exception
* @return
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map handleMethodArgumentTypeMismatchException(
MethodArgumentTypeMismatchException exception,
HttpServletRequest request) {
String msg = request.getRequestURI() +
": " + "请求地址参数" + exception.getValue() + "错误";
return Map.of("message", msg);
}
/**
* 自定义异常的捕获
*
* @param exception
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map handleCustomException(CustomException exception) {
return Map.of("message", exception.getMessage());
}
}
Vue
1. MVVM设计思想
- ViewModel,协助处理显示逻辑
- 将V中的显示内容,与VM中的数据绑定
- 当VM中的数据发生变化时,通知V同步更新显示数据
将View的状态和行为抽象化,将视图UI和业务逻辑分离(之前的jsp只能重新获取HTML页面,并且渲染,现在异步仅获取json格式的所需数据,通过ViewModel渲染)
2. Webpack/ESLint/ECMAScript/Node.js/npm等前端技术规范及工具
- ECMAScript,脚本语言规范,JS语言是该规范的一个实现
- TypeScript,微软开发维护的一个实现了ES规范的,强类型的JS语言,兼容JS的一个超集
- Node.js,开源,基于chrome JS V8引擎的,可运行JS代码的服务器端执行环境,使JS实现服务器端编程
- NPM,JS工程模块依赖管理工具
- Webpack,前端资源模块化管理与打包工具
前端资源模块化管理打包工具,可以将松散的模块,按照依赖和规则打包成符合生产环境部署的前端资源,支持按需异步加载模块,支持基于部署调试不同环境的编译打包
- Bootstrap,CSS框架,优化HTML页面效果,并有一系列的第三方插件
3. vue项目从构建到运行,使用的工具,及命令
- 安装node.js(其自带npm,配置淘宝镜像
npm config set registry http://registry.npm.taobao.org/
) - 命令行install vue手脚架vue/cli:
npm install -g @vuew/cli
-g 全局安装 - 命令行启动vue项目管理器工具图形化界面(启动浏览器,同时创建工程):
vue ui
- 使用Vue 项目管理器,选择目标路径,创建新项目
- 进行手动预设配置 确定babel,router,vuex,linter/formatter,使用配置文件,共5项为打开状态
- 统一项目 基于eslint的代码检测以及prettier的代码风格
npm run serve
启动运行
npm run build
打包运行
项目结构:
4. 组件响应式数据的声明方法,在模板的绑定方法
当一个Vue实例被创建时,data()方法返回值中声明的属性,自动为Vue响应式属性,只有在data() return中预定义的属性才支持响应式更新
模板绑定的方式:{{ msg }}
data() {
return {
msg: "hello",
user: { username: "Tan", level: "admin" }
};
}
data: () => ({
msg:"hello",
user: { username: "Tan", level: "admin" },
})
5. 表单数据的双向绑定方法
- v-model指令在表单元素上创建双向数据绑定
- v-model会忽略所有表单元素的 value、checked、selected 特性的初始值,而总是将 Vue 实例的数据作为数据来源。
<template>
<div>
<form>
<input type="text" v-model="user.name" />
<br />
<label>
<input type="radio" v-model="user.sex" value="male" />
男
</label>
<br />
<label>
<input type="radio" v-model="user.sex" value="female" />
女
</label>
<br />
<select v-model="user.title">
<option v-for="(t, index) in titles" :key="index" :value="{ id: t.id }">
{{ t.name }}
</option>
</select>
<br />
<template v-for="(c, index) in courses">
<label :key="`lab-${index}`">
<input
type="checkbox"
v-model="user.courses"
:value="{ id: c.id }"
:key="`ch-${index}`"
/>
{{ c.name }}
</label>
<br :key="`br-${index}`" />
</template>
<button @click="submit" type="button">提交</button>
</form>
<p>{{ user }}</p>
</div>
</template>
<script>
export default {
data: () => ({
user: {
name: null,
sex: null,
courses: [],
title: null
},
file: {},
titles: [
{ id: 1, name: "讲师" },
{ id: 2, name: "副教授" },
{ id: 3, name: "教授" }
],
courses: [
{ id: 4, name: "Java" },
{ id: 5, name: "Web开发技术" },
{ id: 6, name: "系统程序设计" }
]
}),
methods: {
submit() {
console.log(this.user);
}
}
};
</script>
6. 组件的路由与渲染
router->index.js中:路由路径的定义
const routes = [
// {
// path: "/",
// name: "Home",
// component: Home
// },
{
path: "/example01",
component: () => import("@/views/Eample01.vue")
},
{
path: "/example02",
component: () => import("@/views/Example02.vue")
}
];
const router = new VueRouter({
routes
});
export default router;
<template>
<div id="app">
<div>
<ul>
<li><router-link to="/example01">单向绑定</router-link></li>
<li><router-link to="/example02">双向绑定</router-link></li>
</ul>
</div>
<router-view id="router" :key="$route.path" />
</div>
</template>
7. 路由参数的传递与获取方法
在路由配置文件中,使用:param
,声明参数变量
1.可通过,$route.params
,获取路由参数
this.$route.params.sid
2.可通过vue props属性获取路由参数。
路由声明中:
组件中:
8. 延迟加载组件的引用方法
原单页面开发中,将所有组件统一编译打包,在用户首次请求时全部响应给客户端。由于将用户当前还无需使用的代码全部返回,致使首屏加载显示过慢。
延迟加载组件。声明为延迟加载组件,在编译时将编译为独立的文件,在组件真正渲染时,下载相应组件文件渲染,可极大的增加互交体验
路由延迟加载:
Component: () => import (“@/views/example”)
组件延迟加载:
Component:{ sidebar: () => import(“@/views/Sidebar”)}
编程题
1. 组件及基于指令的响应式数据渲染
v-if v-show
<template>
<div>
<h1>v-text</h1>
<p>
<span v-text="message"></span>
</p>
<h1>v-if</h1>
<p v-if="user.level == 'admin'">此内容仅admin可见</p>
<template v-if="userNameLogin">
<p>第1个input会被复用,输入的信息切换时会保存</p>
<input placeholder="Enter your username" key="key1" />
<br />
<input type="password" placeholder="Enter your password" />
</template>
<template v-else>
<input placeholder="Enter your KEY" key="key2" />
</template>
<br />
<button v-on:click="changeInput">切换</button>
<h1>v-show</h1>
<p v-show="close">此内容仅close为true可见</p>
</div>
</template>
<script>
export default {
data: () => ({
message: "hello world",
admin: true,
userNameLogin: true,
user: { username: "BO", level: "admin" },
close: true
}),
methods: {
changeInput() {
this.userNameLogin = !this.userNameLogin;
}
}
};
</script>
v-bind
<template>
<div>
<h1>v-bind</h1>
<p>
<label>
<input type="checkbox" @click="setAgree" />
同意以上条款
</label>
<br />
<button :disabled="disabled">提交</button>
</p>
<p
@mouseover="actived"
@mouseleave="deActived"
:class="{ 'bg-red': active }"
>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente beatae
facere doloribus ea, enim aliquid illo, dolores similique velit asperiores
cupiditate doloremque ipsa fuga quos! Aliquam eaque perspiciatis delectus
suscipit!
</p>
</div>
</template>
<script>
export default {
data: () => ({
disabled: true,
active: false
}),
methods: {
setAgree() {
this.disabled = !this.disabled;
},
actived() {
this.active = true;
},
deActived() {
this.active = false;
}
}
};
v-for
<template>
<div>
<h1>v-for</h1>
<ul>
<li v-for="h in homeworks" :key="h.id">
<router-link :to="`/homeworks/${h.id}`">
{{ h.name }} / {{ formatDate(h.deadline) }}
</router-link>
</li>
</ul>
<table>
<thead>
<tr>
<th>#</th>
<th>name</th>
<th>deadline</th>
<th>do</th>
</tr>
</thead>
<tr v-for="(h, index) in homeworks" :key="index">
<td>{{ index }}</td>
<td>{{ h.name }}</td>
<td>{{ formatDate(h.deadline) }}</td>
<td>
<button @click="removeItem(index)">remove item</button>
</td>
</tr>
</table>
<p>
动态追加数组中的数据
<br />
this.$set(vm.items, indexOfItem, newValue)
</p>
<button @click="addItem">add item</button>
</div>
</template>
<script>
export default {
data: () => ({
homeworks: [
{ id: 1, name: "Java基本数据类型", deadline: "2019-04-10T09:00" },
{ id: 2, name: "Java封装", deadline: "2019-05-10T12:00" },
{ id: 3, name: "Java泛型", deadline: "2019-06-10T21:30" }
]
}),
computed: {
formatDate() {
return date => date.replace("T", " ").substring(0, 16);
}
},
methods: {
addItem() {
this.$set(this.homeworks, this.homeworks.length, {
id: this.homeworks.length + 1,
name: "Java多线程",
deadline: new Date().toISOString()
});
},
removeItem(index) {
this.$delete(this.homeworks, index);
}
}
};
</script>
2. Vuex中声明全局数据,同步事件更新;组件模板绑定vuex store数据,激活同步更新事件修改数据
import Vue from "vue";
import Vuex from "vuex";
import * as types from "./types";
import axios from "@/axios/MyAxios";
import { updateRoutes } from "@/router/index";
import { author } from "@/util/Const";
Vue.use(Vuex);
const myState = {
exception: { message: null },
isLogin: false,
name: null,
user: {
name: "BO",
address: "956"
},
homework: { name: null, deadline: null },
teacher: null,
courses: null
};
const myMutations = {
[types.UPDATE_USER](state, data) {
state.user = data;
},
[types.LIST_HOMEWORKS](state, data) {
state.homeworks = data;
},
[types.GET_HOMEWORK](state, data) {
state.homework = data;
},
[types.GET_EXCEPTION](state, data) {
state.exception = data;
},
[types.LOGIN](state, data) {
state.isLogin = data;
},
name(state, data) {
console.log(data);
state.name = data;
},
teacher(state, data) {
state.teacher = data;
},
courses(state, data) {
state.courses = data;
}
};
const myActions = {
[types.UPDATE_USER]({ commit }, data) {
setTimeout(() => {
commit(types.UPDATE_USER, data);
}, 2000);
},
async [types.LIST_HOMEWORKS]({ commit }, data) {
let resp = await axios.get("homeworks");
commit(types.LIST_HOMEWORKS, resp.data.homeworks);
},
async [types.GET_HOMEWORK]({ commit }, data) {
let resp = await axios.get(`homeworks/${data.hid}`);
commit(types.GET_HOMEWORK, resp.data.homework);
},
// async [types.GET_HOMEWORK]({ commit }, data) {
// let resp = await axios.get(`homeworks/${data.hid}`);
// return Promise.resolve(resp.data.homework);
// },
// 登录
async [types.LOGIN]({ commit }, data) {
let resp = await axios.post("login", data);
let auth = resp.headers[author];
if (auth != null) {
sessionStorage.setItem(author, auth);
sessionStorage.setItem("role", resp.data.role);
updateRoutes();
commit(types.LOGIN, true);
}
},
async index({ commit }, data) {
let resp = await axios.get("index");
commit("name", resp.data.name);
},
// ------以下为向springboot发出请求
// 需要取消mock,配置后端跨域
async backendindex({ commit }, data) {
let resp = await axios.get("teacher/index");
commit("teacher", resp.data.teacher);
commit("courses", resp.data.courses);
}
};
export default new Vuex.Store({
state: myState,
mutations: myMutations,
actions: myActions,
modules: {}
});
// --------------------------
// 执行时判断,刷新时检测;也可以添加长度等更严格判断
if (sessionStorage.getItem(author) != null) {
myState.isLogin = true;
}
<template>
<div>
<h1>State绑定</h1>
<p>
通过根组件store对象绑定
<br />
{{ getUser.name }} / {{ getUser.address }}
</p>
<hr />
<p>
通过mapState绑定
<br />
{{ user.name }}
</p>
<p>
通过自定义组件内计算属性,引用state属性
{{ user1.address }}
</p>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
getUser() {
return this.$store.state.user;
},
...mapState(["user"]),
...mapState({ user1: state => state.user })
}
};
</script>
<template>
<div>
<h1>Mutation</h1>
{{ user.name }} / {{ user.address }}
<p>
<input type="text" v-model="myUser.name" />
<br />
<input type="text" v-model="myUser.address" />
<br />
<button type="button" @click="change">change</button>
</p>
</div>
</template>
<script>
import { mapState } from "vuex";
import { UPDATE_USER } from "@/store/types.js";
export default {
data: () => ({
myUser: {
name: null,
address: null
}
}),
methods: {
change() {
this.$store.commit(UPDATE_USER, {
name: this.myUser.name,
address: this.myUser.address
});
}
},
computed: {
...mapState(["user"])
}
};
</script>
<template>
<div>
<h1>Actions</h1>
<p>异步更新,支持网络请求等操作</p>
{{ user.name }} / {{ user.address }}
<p>
<input type="text" v-model="myUser.name" />
<br />
<input type="text" v-model="myUser.address" />
<br />
<button type="button" @click="change">change</button>
</p>
</div>
</template>
<script>
import { mapState } from "vuex";
import { UPDATE_USER } from "@/store/types.js";
export default {
data: () => ({
myUser: {
name: null,
address: null
}
}),
methods: {
change() {
this.$store.dispatch(UPDATE_USER, this.myUser);
}
},
computed: {
...mapState(["user"])
}
};
</script>
3. 实体类、双向one to many关联关系的声明
@Data
@NoArgsConstructor
@Entity
public class Course04 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@OneToMany(mappedBy = "course") //one端放弃管理 交给elective的course属性
private List<Elective04> electives;
}
@Entity
@Data
@NoArgsConstructor
public class Elective04 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String detail;
@ManyToOne
private Student04 student;//相当于充当 多对多关系的中间表
@ManyToOne
private Course04 course;
}
@Data
@NoArgsConstructor
@Entity
public class Student04 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@OneToMany(mappedBy = "student") //one端放弃管理 交给elective的student属性
private List<Elective04> electives;
}
4. 基于JPQL语句隐式查询关联对象,参数的赋值
@Repository
public interface Address07Repository extends BaseRepository<Address07, Integer> {
@Query("select a.user from Address07 a where a.detail=:detail")
List<User07> list(@Param("detail") String detail);
@Query("select a.user from Address07 a where a.user.id=:uid")
User07 find(@Param("uid") int uid);
@Query("select a.user from Address07 a where a.detail=:detail and a.user.name=:uname")
List<User07> list(@Param("detail") String detail, @Param("uname") String uname);
@Query("from Address07 a where a.detail=:detail")
Page<Address07> list(@Param("detail") String detail, Pageable pageable);
}
@Repository
public interface User07Repository extends BaseRepository<User07, Integer> {
@Query("from User07 u where u.name=:name")
List<User07> list(@Param("name") String name);
List<User07> findByName(String name);
@Modifying
@Query("update User07 u set u.name=:newname where u.id=:id")
int update(@Param("id") int id, @Param("newname") String name);
}