學校選修的課程的期末考試
總體包括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);
}