REEEEX

KEEP GOING


  • 首页

  • 归档

  • 标签

  • 关于

Blog System | Note-5

发表于 2018-07-31

Blog System | Note-5

@2018年7月31日 10:44:13 @Knowledge From Imooc

权限管理

功能:

建立角色与资源的关系、CSRF问题、启用方法级别的安全设置、使用BCrypt加密、登录、记住我

需求:

角色授权、权限设置

API:
请求方式 路径 参数
GET /login 获取登录页面
POST /login 登录(username,password,remember-me)
应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 改造SecurityConfig

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的安全设置
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String KEY = "rex.com";

@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
// 加密设置
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
/**
* 自定义配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll().antMatchers("/h2-consele/**").permitAll()
// 相应角色访问
.antMatchers("/admins/**").hasRole("ADMIN").and()
// 基于表单登录验证
.formLogin()
// 自定义登录界面
.loginPage("/login").failureUrl("/login-error")
// 启用remember-me
.and().rememberMe().key(KEY)
// 处理异常,拒绝访问就重定向到403
.and().exceptionHandling().accessDeniedPage("/403");
http.csrf().ignoringAntMatchers("/h2-console/**");
http.headers().frameOptions().sameOrigin();
}

/**
* 认证信息管理
* @param auth
* @throws Exception
*/
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
}

// 修改UserServiceImpl
@Service
public class UserServiceImpl implements UserService,UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
CSRF防护(详见附录)

CSRF:跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用

1
2
3
4
<!-- CSRF -->
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

防护:“双提交”cookie。此方法只工作于Ajax请求,但它能够作为无需改变大量form的全局修正方法。如果某个授权的cookie在form post之前正被JavaScript代码读取,那么限制跨域规则将被应用。如果服务器需要在Post请求体或者URL中包含授权cookie的请求,那么这个请求必须来自于受信任的域,因为其它域是不能从信任域读取cookie的

1
2
3
4
5
6
7
8
9
10
// 获取 CSRF Token 
var csrfToken = $("meta[name='_csrf']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
$.ajax({
...
beforeSend: function(request) {
request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token
},
...
});

博客管理

需求:

发表、编辑、删除、分类、标签、上传图片、模糊查询、最新、最热、阅读量统计

功能:

用户主页、个人资料设置、头像更换

文件服务器(图片、文件等)

MongoDB File Server

基于 MongoDB 的文件服务器;MongoDB File Server 致力于小型文件的存储,比如图片、文档等;由于MongoDB 支持多种数据格式的存储,对于二进制的存储自然也是不话下,可以方便用于存储文件;

API:
请求方式 路径 参数
GET /u/{username} 具体某个用户主页(username)
GET /u/{username}/profile 获取个人设置页面(username)
POST /u/{username}/profile 保存个人设置(username,User)
GET /u/{username}/avatar 获取个人头像(username)
POST /u/{username}/avatar 保存个人头像(username,User)
GET /u/{username}/blogs 查询用户博客(order,catalog,keyword,async,pageIndex,pageSize)
GET /u/{username}/blogs/edit 获取新增博客的页面(username)
POST /u/{username}/blogs/edit 保存博客的页面(username,Blog)
GET /u/{username}/blogs/edit/{id} 编辑博客的页面(username,id)
DELETE /u/{username}/blogs/edit/{id} 删除博客(username,id)
应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 添加 Markdown parser 依赖
compile('es.nitaur.markdown:txtmark:0.16')

// 添加文件服务器 application.properties
# 文件服务器
file.server.url=http://localhost:8081/upload

// UserspaceController
@GetMapping("/{username}/profile")
@PreAuthorize("authentication.name.equals(#username)")
public ModelAndView profile(@PathVariable("username")String username,Model model){
User user = (User)userDetailsService.loadUserByUsername(username);
model.addAttribute("user",user);
model.addAttribute("fileServerUrl",fileServerUrl);
return new ModelAndView("/userspace/profile","userModel",model);
}

@PostMapping("/{username}/profile")
@PreAuthorize("authentication.name.equals(#username)")
public String profile(@PathVariable("username") String username, User user) {
User originalUser = userService.getUserById(user.getId());
originalUser.setEmail(user.getEmail());
originalUser.setName(user.getName());
// 判断密码是否更改
String rawPassword = originalUser.getPassword();
PasswordEncoder encoder = new BCryptPasswordEncoder();
String encodePassword = encoder.encode(user.getPassword());
boolean isMatch = encoder.matches(rawPassword, encodePassword);
if (!isMatch) {
originalUser.setEncodePassword(user.getPassword());
}
userService.saveOrUpdateUser(originalUser);
return "redirect:/u/" + username + "/profile";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Blog.java
@Entity
public class Blog {...}

// BlogRepository.java
public interface BlogRepository extends JpaRepository<Blog,Long> {...}

// BlogServiceImpl.java
@Service
public class BlogServiceImpl implements BlogService {...}

// Modify UserspaceController.java
@GetMapping("/{username}")
public String userSpace(@PathVariable("username") String username, Model model) {...}

@GetMapping("/{username}/blogs")
public String listBlogsByOrder(@PathVariable("username") String username,
@RequestParam(value = "order", required = false, defaultValue = "new") String order,
@RequestParam(value = "category", required = false) Long catalogId,
@RequestParam(value = "keyword", required = false, defaultValue = "") String keyword,
@RequestParam(value = "async", required = false) boolean async,
@RequestParam(value = "pageIndex", required = false, defaultValue = "0") int pageIndex,
@RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize,
Model model) {...}

@GetMapping("/{username}/blogs/{id}")
public String getBlogById(@PathVariable("username") String username,
@PathVariable("id") Long id, Model model) {...}


@GetMapping("/{username}/blogs/edit")
public ModelAndView createBlog(@PathVariable("username")String username,Model model) {...}

附录

CSRF防护
MongoDB File Server

@2018年7月31日 14:40:19

Blog System | Note-4

发表于 2018-07-30

Blog System | Note-4

@2018年7月30日 15:18:07 @Knowledge From Imooc

  • 基础知识
  • Note1
  • Note2
  • Note3

整体框架实现

需求设计(见Note-1)

后台整体控制层 Controller

MainController/BlogController/UserspaceController/AdminController/UserController

后台API

请求方式 API 参数
GET /blogs order,keyword,async,pageIndex,pageSize
GET /u/{username} 具体用户的主页
GET /u/{username}/profile 用户的个人设置
POST /u/{username}/profile 保存个人设置 username,User
GET /u/{username}/avatar 获取个人头像 username
POST /u/{username}/avatar 保存个人头像 username
GET /u/{username}/blogs 查询用户博客
order,catalog,keyword,async,pageIndex,pageSize
GET /u/{username}/blogs/edit 获取新增文章的页面 username
POST /u/{username}/blogs/edit 新增、编辑文章 username,Blog
GET /u/{username}/blogs/edit/{id} 获取编辑某文章的界面 username,id
DELETE /u/{username}/blogs/edit/{id} 删除文章
GET /login 登录
POST /login username,password,remember-me
GET /register 注册
POST /register 注册成功,跳转至登录 User
GET /users 用户列表 async,pageIndex,pageSize,name
GET /users/add 新增用户界面
POST /users 保存用户 User,authorityId
DELETE /users/{id} 删除用户 id
GET /users/edit/{id} 获取某个用户的编辑界面 id
GET /comments 获取评论列表 blogId
POST /comments 保存评论 blogId,commentContent
DELETE /comments/{id} 删除评论 id,blogId
POST /votes 保存点赞 blogId
DELETE /votes 删除点赞 blogId
GET /catalogs 获取用户分类列表 username
POST /catalogs 保存用户雷芬 username,CatalogVO(username,Catalog)
GET /catalogs/edit/{id} 获取某ID分类的编辑界面
DELETE /catalogs/{id} 删除分类 id,username

前端整体布局

/ — index.html / login.html / register.html

admins — index.html

fragments — header.html / footer.html / page.html

users — form.html / list.html / view.html

userspace — blog.hmtl / blogedit.html / u.html

用户管理 & 角色管理

注册、登录(在授权管理实现)、增加用户、修改用户、删除用户、搜索用户

1
2
3
4
5
6
7
POST 	/register
GET /register(User)
GET /users(async,pageIndex,pageSize,name);
GET /users/add;
POST /users;
DELETE /users/{id};
GET /users/edit/{id};
应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 修改User实体
@NotEmpty(message = "姓名不能为空")
@Size(min = 2, max = 20)
@Column(nullable = false, length = 20)

// 新增Authority实体
@Entity
public class Authority implements GrantedAuthority{...}

// 编写Repository
public interface UserRepository extends JpaRepository<User,Long> {...}
public interface AuthorityRepository extends JpaRepository<Authority,Long> {...}

// 完成Service
public interface UserService {...}

// 完成Impl
@Service
public class UserServiceImpl implements UserService {...}

// VO
public class Response {...}

// Util
public class ConstraintViolationExceptionHandler {
/**
* 获取批量异常信息
* @param e
* @return
*/
public static String getMessage(ConstraintViolationException e){
List<String> msgList = new ArrayList<>();
for(ConstraintViolation<?> constraintViolation : e.getConstraintViolations()){
msgList.add(constraintViolation.getMessage());
}
String messages = StringUtils.join(msgList.toArray(),";");
return messages;
}
}

// 对应Controller
AdminController,MainController,UserController
// 前端页面
略

附录

null

@2018年7月30日 20:21:17

Blog System | Note-3

发表于 2018-07-29

Blog System | Note-3

@2018年7月30日 10:16:35 Knowledge From Imooc

集成Bootstrap

Bootstrap是什么

​ 基于HTML、CSS、JS的前端框架;响应式布局;移动设备优先;

​ 响应式meta标签;Normalize.css;使用Normalize建立跨浏览器的一致性;Reboot;

HTML5 doctype
1
2
3
<!DOCTYPE html>
<html lang="en">
</html>
Bootstrap网格系统

Q:什么是移动设备优先策略

A:基础的CSS是移动优先,优先设计更小的宽度;媒体查询(针对平板,PC);渐进增强(随屏幕大小增加而添加元素);响应式(viewport尺寸的增加,系统自动分为十二列);

架构设计与分层

Q:为什么需要分层

A: 不分层时,数据访问,数据获取,数据判断,数据展示冗杂在同一个文件当中;代码职责不准确,难以拓展;代码混杂,难以维护;代码无分工,难以组织;

​ 分层时:将业务功能分层(显示层,业务层,数据访问层);良好的层次关系(上层依赖于下层,下层支撑上层,但下层不能直接访问上层);每层保持独立;

Q:三层架构

A:展示层(Persentation Layer):提供与用户交互的界面;

业务层(Business Layer):用于实现业务逻辑;

数据访问层(Data Access Layer):与数据库交互的一层;

Q:博客系统的架构设计及职责划分

A:表示层(Controller,View)、业务层(Entity,VO,Service)、数据访问层(Repository)

职责划分:博客系统—RESTful API—文件管理系统;

博客系统:关系型数据库,NoSQL(ElasticSearch)

文件管理系统:NoSQL(MongoDB)

应用@

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout" data-th-fragment="header">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Tether core CSS -->
<link href="/css/tether.css" th:href="@{/css/tether.css}" type="text/css" rel="stylesheet">
<script src="https://cdn.bootcss.com/tether/1.4.4/js/tether.js"></script>
<!-- Bootstrap CSS -->
<link href="/css/bootstrap.css" th:href="@{/css/bootstrap.css}" type="text/css" rel="stylesheet">

<!-- Font-Awesome CSS -->
<link href="/css/font-awesome.css" th:href="@{/css/font-awesome.css}" type="text/css" rel="stylesheet">

<!-- NProgress CSS -->
<link href="/css/nprogress.css" th:href="@{/css/nprogress.css}" type="text/css" rel="stylesheet">

<!-- thinker-md CSS -->
<link href="/css/thinker-md.vendor.css" th:href="@{/css/thinker-md.vendor.css}" type="text/css" rel="stylesheet">

<!-- bootstrap tags CSS -->
<link href="/css/component-tageditor.css" th:href="@{/css/component-tageditor.css}" type="text/css"
rel="stylesheet">

<!-- bootstrap chosen CSS -->
<link href="/css/component-chosen.css" th:href="@{/css/component-chosen.css}" type="text/css" rel="stylesheet">

<!-- toastr CSS -->
<link href="/css/toastr.css" th:href="@{/css/toastr.css}" type="text/css" rel="stylesheet">

<!-- jQuery image cropping plugin CSS -->
<link href="/css/cropbox.css" th:href="@{/css/cropbox.css}" type="text/css" rel="stylesheet">

<!-- Custom styles -->
<link href="/css/style.css" th:href="@{/css/style.css}" type="text/css" rel="stylesheet">
<link href="/css/thymeleaf-bootstrap-paginator.css" th:href="@{/css/thymeleaf-bootstrap-paginator.css}"
type="text/css" rel="stylesheet">

</head>

// 引用header
<head th:replace="~{fragments/header :: header}">
</head>

<body>
<div class="container"></div>
<div th:replace="~{fragments/footer :: footer}">...</div>
</body>
</html>

需求分析

参照Note-1 Core Function

原型设计

提供给用户最终产品的基本效果;方便开发人员进行开发;二次开发;

权限管理(基于角色的权限管理)

角色

代表具有一系列的行为和责任的实体;限定功能范围;用户账号与角色相关联

RBAC

基于角色的访问控制 Role-Based Access Control

隐式访问控制(与角色密切关联)

显示访问控制(与权限关联,权限与角色关联)

解决方案

Apache Shiro、Spring Security

Spring Security

基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架

认证

认证是建立主体的过程;(主体:在应用程序中执行操作的用户、设备或系统)

授权

又称访问控制;授权指决定是否允许主体在应用程序中执行操作

身份验证技术

HTTP BASIC;HTTP Digest;HTTP X.509;LDAP;基于表单的认证;OpenID;单点登录;Remember-Me;匿名身份验证;Run-as;JAAS;

模块

Core;Remoting;Web;Config;LADP;ACL;CAS;OpenID;Test;

应用@

1
2
3
4
// 添加 Spring Security 依赖
compile('org.springframework.boot:spring-boot-starter-security')
// 添加 Thymeleaf Spring Security 依赖
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE')
后台应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 安全配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/css/**","/js/**","/fonts/**","/index").permitAll() // 可访问
.antMatchers("/users/**").hasRole("ADMIN") // 需要角色
.and()
.formLogin() // 基于FORM表单登录验证
.loginPage("/login").failureUrl("/login-error"); // 自定义登录界面
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
// 认证信息管理 存储于内存中
auth.inMemoryAuthentication().withUser("admin").password("123123").roles("ADMIN");
}
}

// 控制器
@Controller
public class MainController {
@GetMapping("/")
public String root(){...}

@GetMapping("/index")
public String index(){...}

@GetMapping("/login")
public String login(){...}

@GetMapping("/login-error")
public String loginError(Model model){...}
}
前端应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!--index.html-->
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
...
<div sec:authorize="isAuthenticated()">
<p>已有用户登录</p>
<p>登录用户为:<span sec:authentication="name"></span></p>
<p>登录角色为:<span sec:authentication="principal.authorities"></span></p>
</div>
<div sec:authorize="isAnonymous()">
<p>未有用户登录</p>
</div>
<!--header.html-->
<!-- 登录判断 -->
<div sec:authorize="isAuthenticated()" class="row">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<span class="nav-link" sec:authentication="name"></span>
</li>
</ul>
<form th:action="@{/logout}" method="post">
<input class="btn btn-outline-success" type="submit" th:value="登出">
</form>
</div>
<div sec:authorize="isAnonymous()">
<a th:href="@{~/login}" class="btn btn-success my-2 my-sm-0" type="submit">登录</a>
</div>
<!--login.html-->
<form th:action="@{~/login}" method="post">
<div class="form-group col-md-5">
<label for="username" class="col-form-label">帐号</label>
<input type="text" class="form-control" id="username" maxlength="50">
</div>

<div class="form-group col-md-5">
<label for="password" class="col-form-label">密码</label>
<input type="password" class="form-control" id="password" maxlength="50">
</div>
<div class="form-group col-md-5">
<button type="submit" class="btn btn-primary">登录</button>
</div>
<!--登录错误的提示信息-->
<div class="form-group col-md-5" th:if="${loginError}">
<p class="blog-label-error" th:text="${errMsg}"></p>
</div>
</form>

附录

BootCDN

Bootstrap

Bootstrap-CSS

@2018年7月30日 15:17:49

Blog System | Note-2

发表于 2018-07-28

Blog System | Note-2

@2018年7月29日 14:38:38 Knowledge From Imooc

数据持久化

JPA

​ 用于管理Java EE和Java SE环境中的持久化,以及对象/关系(O/R)映射的Java API;

实体@Entity

​ 表示数据库中的表,每个实体实体对应于表中的行;

​ 类必须有一个public或protected的无参构造函数;

​ 实体实例被当作值以分离对象方式进行传递,则该类必须实现Serializable接口;

​ 唯一的标识:主键或复合主键;

关系 Relation

​ 一对一:@OneToOne

​ 一对多:@OneToMany

​ 多对一:@ManyToOne

​ 多对多:@ManyToMany

管理实体的接口 EntityManager

​ 定义用于持久性上下文进行交互的方法;

​ 创建和删除持久实体实例,通过实体的主键查找实体;

​ 允许在实体上运行查询;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取EntityManager实例
@PersistenceUnit // 持久化单元
EntityManagerFactory emf;
EntityManager em;
@Resource
UserTransaction utx;
...
em = emf.createEntityManager();
try{
utx.begin();
em.persist(SomeEntity);
em.merge(AnotherEntity);
em.remove(ThirdEntity);
utx.commit();
}catch(Exception e){
utx.rollback();
}
1
2
3
4
5
6
7
8
// 查找实体
@PersistenceContext
EntityManager em;
public void enterOrder(int custID,CustomerOrder newOrder){
Customer cust = em.find(Customer,custID);
cust.getOrders().add(newOrder);
newOrder.setCustomer(cust);
}
Spring Data JPA

​ 更大的Spring Data家族的一部分;

​ 对基于JPA的数据访问层的增强支持;

​ 更容易构建基于Spring数据访问技术栈的程序;

常用接口

​ CrudRepository

​ PagingAndSortingRepository

​ 自定义接口:根据方法名创建查询 public interface UserRepository extends Repository<User,Long>{}

应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
// Controller
findAll();
findOne();
delete();
save();
// application.properties
# 使用H2控制台:
spring.h2.console.enabled=true
#查看
localhost:8080/h2-console
// application.properties
# DataSource
spring.datasource.url=...
spring.datasource.username=...
spring.datasource.password=...
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop

全文搜索

全文搜索

​ 概念:将文件中所有文本与搜索项匹配的文字资料检索方法

​ Knowledge About(数据结构 && 非结构化数据的检索)

​ 实现原理:建立文本库、建立索引、执行搜索、过滤结果

​ 实现技术:Lucene、ElasticSearch(ES)、Solr

ElasticSearch

​ 高度可扩展的开源全文搜索和分析引擎;

​ 快速的、近实时性的对大数据进行存储、搜索和分析;

​ 用于支持有复杂的数据搜索需求的企业级应用;

特点:分布式(分布到多个分片上)、高可用(由分布式所得)、多类型(多种数据类型)、多API、面向文档(不需要事先定义模式)、异步写入(性能高于同步)、近实时、基于Lucene、Apache协议

核心概念:

近实时:

​ 搜索文档到可搜索之间的时间,轻微的延时,1s左右;

​ 每隔n秒刷新数据;

​ 索引建立后,不会直接写入磁盘,存在文件系统缓存中,根据刷新策略,定期同步到磁盘中;

集群:

​ 一个或多个节点的集合,保存应用的全部数据, 并提供基于全部节点的集成式的索引和搜索功能;

​ 每个集群有唯一名称;

节点:

​ 集群中的一台服务器,用于保存数据,并且参与整个集群的索引和搜索;

索引(5个分片,1个副本):

​ 加快搜索速度,相似文档的集合;

​ 在单个集群中,根据需要,定义任意数量的索引;

类型:

​ 对一个索引中的文档中,做进一步的细分,根据公共属性划分;

文档:

​ 进行索引的基本单位,JSON格式;

分片:

​ 超出单个节点处理的范围,将索引分成多个分片(含副本),存储索引的部分数据;

副本:

​ 分片的故障不可避免,为了可靠性,设置分片副本,分配到不同节点上;

​ 增加吞吐量,分配到不同副本中;

应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// build.gradle

// 添加 Spring Daat Elasticsearch 的依赖
compile('org.springframework.boot:spring-boot-starter-data-elasticsearch')
// 添加 JNA 的依赖
compile('net.java.dev.jna:jna:4.3.0')

// application.properties
# Elasticsearch 服务地址
spring.data.elasticsearch.cluster-nodes=localhost:9300
# 设置连接超时时间
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s

// domain
@Document(indexName="blog",type="blog")

// repository
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog,String> {...}

进行controller之前,需要启动elasticsearch服务器
PS:注意jar包的版本,以及elasticsearch服务器的版本问题

// controller
@GetMapping
public List<EsBlog> list(@RequestParam(value = "title")String title,
@RequestParam(value = "summary")String summary,
@RequestParam(value = "content")String content,
@RequestParam(value = "pageIndex",defaultValue = "0")int pageIndex,
@RequestParam(value = "pageSize",defaultValue = "10")int pageSize){
// 数据在测试用例中初始化
Pageable pageable = new PageRequest(pageIndex, pageSize);
Page<EsBlog> page = esBlogRepository.find...(title,summary,content,pageable);
return page.getContent();
}

附录

数据结构

​ 结构化:具有固定格式或有限长度的数据(数据库、元数据)

​ 非结构化:不定长或无固定格式的数据(邮件,word文档)

非结构化数据的检索

​ 顺序扫描 Serial Scanning : 从头到尾搜索

​ 全文搜索 Full-text Search : 将非结构化数据部分信息,组织成结构化数据,添加索引,进行搜索

2018年7月29日 22:35:47

Blog System | Note-1

发表于 2018-07-27

Blog System | Note-1

@2018年7月29日 09:49:20 Knowledge From Imooc

Core Function

用户管理:注册、登录、增加、修改、删除、搜索

安全设置:角色授权、权限设置

博客管理:发表、编辑、删除、分类、设置标签、上传图片、模糊查询、排序、统计

评论管理:发表、删除、统计

点赞管理:点赞、取消、统计

分类管理:创建、编辑、删除、查询

标签管理:创建、删除、查询

首页搜索:全文检索、最新文章、最热文章、热门标签、用户、文章、最新发布

Core Technology

FrontEnd:Bootstrap、Thymeleaf、jQuery、HTML5、JS、CSS

BackEnd:Spring、Spring Boot、Spring MVC、Spring Data、Spring Security、Hibernate

DataSource:MySQL、H2、MongoDB(存储非结构化数据)

Other:ElasticSearch(搜索)、Gradle(管理构建)

Thymeleaf & Spring Boot

Thymeleaf是Java模版引擎,自然模版,原型即页面,OGNL,SpringEL,遵守WEB标准,支持HTML5

Thymeleaf标准方言

1
2
3
> <span th:tex=""> 引入命名空间
> <span data-th-text=""> 自定义属性
>

标准表达式,设置属性值,迭代器,条件语句,模版布局,属性优先级,注释,内联,基本对象,工具对象

Thymeleaf基本入门

变量表达式

语法:${…}

1
2
> <span th:text="${book.author.name}">
>
消息表达式(文本外部化、国际化、i18n)

语法:#{…}

1
2
> <th th:text="#{header.address.city}">
>
选择表达式

语法:*{}

1
2
3
4
5
> <div th:object="${book}">
> ...
> <span th:text="*{title}">...</span>
> </div>
>

与变量表达式的区别:选择表达式是在当前选择的对象,而不是全部上下文变量映射执行

链接表达式

语法: @{}

1
2
> <a th:href="@{http://www.baidu.com}" ... </a>
>
分段表达式

语法: th:insert 或 th:replace

字面量(文字),算术操作,比较和等价,三元运算符,无操作(_):

文字,数字,布尔,null

+,-,*,/,%

.>,<,==,!=,>=,<=

设置属性值(Setting Attribute Values)

设置任意属性值(Setting the value of any attribute)

1
2
3
4
5
> <from action="subscribe.html" th:attr="action=@{/subscribe}">
> ==> <from action="/abc/subscribe">
> <input type="submit" value="Subscribe" th:attr="value=#{subscribe.submit}"/>
> ==> <input type="submit" value="Subscribe">
>

设置值到指定的属性(Setting value to specific attributes)

1
2
3
> <from action="subscribe.html" th:action="@{/subscribe}">
> <input type="submit" value="Subscribe" th:value="#{subscribe.submit}"/>
>

固定值布尔属性(Fixed-value boolean attributes)

1
2
3
4
> <input type="checkbox" name="option2" checked /> <!-- HTML -->
> <input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->
> <input type="checkbox" name="active" th:checked="${user.active}" />
>
迭代器 Iteration

基本迭代 th:each

1
2
> <li th:each="book" : ${book}  th:text="${book.title}">Book Of Java</li>
>

状态变量

​ -index,count,size,current,even/odd,first,last

1
2
3
4
5
6
7
8
9
10
11
> <table>
> <tr>
> <th>NAME</th>
> <th>IN STOCK</th>
> </tr>
> <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
> <td th:text="${prod.name}">Onions</td>
> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
> </tr>
> </table>
>
条件语句 Conditional Evaluation

th:if th:unless

1
2
3
4
5
> <a href="comments.html" 
> th:href="@{/product/comments(prodId=${prod.id})}"
> th:if(unless)="${not #lists.isEmpty(prod.comments)}">view</a>
> </td>
>

th:switch

1
2
3
4
5
6
> <div th:switch="${user.role}">
> <p th:case="'admin'">User is an administrator</p>
> <p th:case="#{roles.manager}">User is a manager</p>
> <p th:case="*">User is some other thing</p>
> </div>
>
模版布局 Template Layout

定义和引用片段

1
2
3
4
5
6
7
> // 定义
> <div th:fragment="copy">
> &copy; 2011 The Good Thymes Virtual Grocery
> </div>
> // 引用
> <div th:insert="~{footer :: copy}"></div>
>

不使用fragments

1
2
3
4
5
6
7
> // 定义
> <div id="copy-section">
> &copy; 2011 The Good Thymes Virtual Grocery
> </div>
> // 引用
> <div th:insert="~{footer :: #copy-section}"></div>
>

th:insert 和 th:replace 和 th:include的区别

​ -th:insert 简单的插入指定的片段作为正文的主标签

​ -th:replace 指定实际片段来替换其主标签

e.g

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> <footer th:fragment="copy">
> &copy; 2018
> </footer>
> // use
> <div th:insert="footer :: copy"></div>
> <div th:replace="footer :: copy"></div>
> <div th:include="footer :: copy"></div>
> // result
> <div>
> <footer>
> &copy; 2018
> </footer>
> </div>
>
> <footer>
> &copy; 2018
> </footer>
>
> <div>
> &copy; 2018
> </div>
>
属性优先级 Attribute Precedence

Q:同一个标签写入多个th:*属性,谁先执行?属性优先级表

A:根据优先级表,与标签前后顺序无关,与优先级有关

1
2
3
<ul>
<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>
</ul>
注释 Comments and Blocks

标准HTML/XML

​ -

Thymeleaf解析器级注释块

​ 删除所有 <!--/* 和 */-- 之间的内容

原型注释块

​ 当模版静态打开时,注释块则不显示;当模版执行时,注释的代码被显示<!--/*/ …/*/-->

1
2
3
4
5
6
7
8
> <span>hello!</span>
> <!--/*/
> <div th:text="${...}">
> ...
> </div>
> /*/-->
> <span>goodbye!</span>
>
内联 Inlining

直接将表达式写入文本当中

[[…]] 对应 th:text 转换特殊符号

1
2
3
> <p>The message is "[[${msg}]]"</p>
> <p>The message is "This is &lt;b&gt;great!&lt;/b&gt;"</p>
>

[(…)] 对应 th:utext utext不转换特殊符号

1
2
3
> <p>The message is "[(${msg})]"</p>
> <p>The message is "This is <b>great!</b>"</p>
>

禁用内联

​ -th:inline=”none”

JS内联

1
2
3
4
5
6
7
8
> <script th:inline="javascript">
> var username = [[${session.user.name}]];
> </script>
> // RESULT
> <script th:inline="javascript">
> var username = "Sebastian \"Fruity\" Applejuice";
> </script>
>
表达式基本对象

#ctx : 上下文对象

#locale:直接访问与java.util.Locale 关联的当前的请求

request/session/application等

Web上下文对象

​ -#request #session #servletContext

Thymeleaf与Spring Boot集成

修改build.gradle

1
2
3
4
ext['thymeleaf.version'] = '3.0.3.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.0'
// 添加 Thymeleaf 的依赖
compile('org.springframework.boot:spring-boot-starter-thymeleaf')

Coding

API设计

GET /users 返回用户列表list.html

GET /users/{id} 返回用户view.html

GET /users/form 返回新增或修改用户的form.html

POST /users 新增或修改用户,成功后重定向

GET /users/delete/{id} 根据id删除用户数据

GET /users/modify/{id} 根据id修改用户数据

数据

未使用数据库或持久化数据层时,可使用ConcurrentMap的方式暂时存储数据在内存当中,充当测试数据

Different
SSM Spring Boot
Service Repository
ServiceImpl RepositoryImpl
Entity Domain

FrontEnd

引入Thymeleaf命名空间

1
2
<html xmlns:th="http://www.thymeleaf.org" 
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout">

具体运用:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div th:replace="~{fragments/header :: header}"></div>
<h3 th:text="${userModel.title}"></h3>
// th:href
<a th:href="@{/users/form}">创建用户</a>
<a th:href="@{'/users/delete/'+${userModel.user.id}}">删除</a>
// th:if
<tr th:if="${userModel.userList.size()} == 0"></tr>
// th:each
<tr th:each="user : ${userModel.userList}">
<td th:text="${user.id}"></td>
<td th:text="${user.email}"></td>
<td><a th:href="@{'/users/'+${user.id}}" th:text="${user.name}"></a></td>
</tr>

附录

一、目录文件解释

buildscript.gradle (代码块中脚本优先执行)

  • ext 用于定义动态属性(sprintBootVersion = ‘2.0.3 RELEASE’)
  • repositories 使用中央仓库以及spring仓库
  • dependencies 依赖关系
  • classpath 声明执行其余脚本时,ClassLoader可使用这些依赖项(可引用ext中动态属性${sprintBootVersion})
  • 使用插件
  • 指定生成编译文件版本 默认jar
  • 依赖关系(编译阶段、测试阶段)

二、Gradle

  1. 按约定声明构建和建设
  2. 强大的支持多工程的构建
  3. 强大的依赖管理(基于Apache Ivy),提供最大的便利去构建工程
  4. 全力支持已有的 Maven 或者Ivy仓库基础建设
  5. 支持传递性依赖管理,在不需要远程仓库和pom.xml和ivy配置文件的前提下
  6. 基于groovy脚本构建,其build脚本使用groovy语言编写
  7. 具有广泛的领域模型支持构建
  8. 深度 API
  9. 易迁移
  10. 自由和开放源码,Gradle是一个开源项目,基于 ASL

三、Thymeleaf

Tutorial:Using Thymeleaf 3.0

@2018年7月29日 14:19:06

REVIEW-JAVA

发表于 2018-07-09

名词解释

1.泛型

​ 允许在定义类、方法、接口时,使用类型形参T,并且在声明变量、创建对象、使用方法时,动态指定实际参数类型,称为类型实参。例如:List\,ArrayList\

2.多态

​ 指编译时类型和运行时类型不一致的现象

​ 编译时类型:声明该变量时,使用的对象类型

​ 运行时类型:程序运行时,实际赋予该变量的对象类型

3.阻塞

​ 当前正在执行的线程进入阻塞状态,允许其他线程获得执行的机会

​ 当阻塞的线程,在sleep时间到、IO方法返回、获得对象锁、收到notify、resume的条件下,可重新进入到就绪状态(并非运行状态)。允许与其他线程竞争CPU资源,等待线程的再次调度

4.封装

​ 封装是面向对象的重要特征之一,封装把对象的属性和操作,作为一个独立的整体,对外隐藏所有的内部实现细节,并且通过受保护的接口访问其他对象

5.继承

​ 在面向对象设计中,继承机制有效组织类的结构,确定类之间的关系

​ 在已有类的基础上,可以定义新的类,达到代码复用、提高开发效率的目的

​ 对象的一个新类可由现有的类派生,称为类的继承。子类允许继承父类的非private方法和实例变量,访问private成员,则需要通过getter、setter方法实现,并且子类可以通过增加和删除新的方法,满足服务需求

6.抽象

​ 抽象是指对象共性的抽取,从正确的角度,展示对象的相关细节

​ 抽象包含两个方面:过程抽象和数据抽象

​ 过程抽象:识别功能语句,并且作为一个工作单元展示的过程

​ 数据抽象:创建复杂的数据类型,只公开与数据类型交互有意义的操作,对外隐藏所有的内部实现细节

简答题

1.什么是可变参数?

​ 在方法定义时,参数类型确定,参数个数不确定,Java把可变参数封装成数组处理

​ 在方法调用时,可传递任意个数的参数,JVM自动把数据封装成数组,交由方法的逻辑进行处理

1
public static void Array(int ... intArray){}

2.JAVA垃圾回收是什么?

​ 垃圾回收是一个自动运行的动态内存管理程序,自动释放不再被引用的对象

​ 使用特定的垃圾回收算法,实现内存资源的动态回收,减轻编程负担

3.什么是初始化数据块?

​ 初始化数据块是仅次于成员变量、方法、构造器的第四种成员,分为静态和非静态初始化数据块

​ 静态初始化数据块:只能初始化static成员

​ 初始化数据块没有名字,不能传入形参,不能通过类、对象调用,总在构造器之前执行

4.JAVA中equals和==之间的区别

​ equals:是String类从超类Object继承而来的方法,用于比较两个对象,比较两个对象的内容是否相等

​ ==:用于比较基本数据类型和引用类型

​ 比较基本数据类型时,比较数据类型和值,相同返回true

​ 比较引用类型时,比较内存中是否指向同一对象,相同返回true

5.简单给出JAVA中实现线程的两种方式

​ 通过Thread和Runnable两种方式,重写run()作为线程的执行体

​ 对于Thread,已经继承Thread类,不能再继承其它类,并且是多任务多线程的操作模式

​ 对于Runnable接口,可以继承其它类,并且是多线程处理同一份资源的操作模式

1
2
3
4
5
6
7
8
public class ThreadTest extends Thread{
public void run(){
// ...
}
public static void main(String[] args){
new ThreadTest().start();
}
}
1
2
3
4
5
6
7
8
9
10
public class RunnableThread implements Runnable{
@Override
public void run(){
//...
}
public static void main(String[] args){
RunnableThread run = new RunnableThread();
new Thread(run,"ThreadName").start();
}
}

6.接口和抽象类的区别

​ 1.接口只能定义静态常量属性,不能定义普通属性;抽象类可以定义两者

​ 2.接口不能定义初始化代码块;抽象类可以定义初始化代码块

​ 3.接口不包含构造器;抽象类可以包含构造器(抽象类的构造不用于创建对象,而是为了让子类调用构造器,完成抽象类的初始化操作)

​ 4.接口不能定义静态方法;抽象类可以定义静态方法

​ 5.接口只能包含抽象方法,不包含已实现的方法;抽象类可以包含普通方法

一个类最多继承一个直接父类,包括抽象类;一个类可以实现多个接口,以弥补JAVA单继承的不足

7.String是基本数据类型吗?基本数据类型有哪些?

​ String不是基本数据类型,是类代表字符串,是引用类型(类、接口、数组)

​ 基本数据类型:byte,char,boolean,short,int,long,float,double

8.给出ArrayList、Vector、LinkedList的存储性能和特性

​ ArrayList和Vector都是数组的方式存储数据,数组元素大小大于实际存储的数据,便于插入和增加;允许直接按序号索引元素,但是插入元素涉及内存的操作,索引快,插入慢

​ Vector使用了Synchronize,性能上较ArrayList差

​ LinkedList实现双向链表存储,按序号索引需要前向、后向遍历,但是插入时只需要记录本项前后的元素即可,索引慢,插入快

9.&和&&的区别

​ &是位运算符,对数进行位运算;&&是逻辑运算符,对逻辑进行运算

10.sleep()和wait()的区别

​ sleep是Thread类的静态方法,使调用线程进入睡眠状态,让出执行机会给其他的线程,直到睡眠时间结束;sleep时间到的线程重新进入就绪状态,与其他线程共同竞争CPU资源;由于是静态方法,不释放对象锁

​ wait是Object类的方法,使得调用线程,释放对象锁,进入到与该对象有关的等待池中,使对象可以给其他线程访问;当等待的线程收到notify时,重新进入到对象锁池中,等待获得对象锁,进入运行状态

11.GC是什么?为什么要有GC?

​ GC是由JAVA提供的垃圾回收机制,是JAVA中自动运行的动态内存管理程序

​ 因为在编程中,内存处理容易忘记或错误的回收,会导致系统的奔溃;并且JAVA没有提供,释放已分配内存的显式操作方法;GC通过自动检测对象是否超过作用域,实现内存的动态回收,防止内存泄漏的问题

编程题

1.UDP编程

a.说明JAVA在UDP协议与TCP协议实现中连接和数据传输的不同

​ UDP:通过端口号区别程序之间的若干通信,数据传输按数据报传输,包的到达先后次序不定;

​ TCP:通过端口号区别程序之间的若干通信,数据传输按字节流传输,按顺达到达

​ UDP是不可靠的网络传输,负载小

​ TCP是可靠的网络传输,负载大,具有建立连接、中断连接、差错检测、流量控制的功能

b.编程举例UDP如何发送数据
1
2
3
4
5
6
7
8
9
10
11
12
public class UDPClient{
public static void main(String[] args) throws Exception{
int port = 12345;
InetAddress address = InetAddress.getByName("localhost");
String data = "hello";
byte[] dataToSend = data.getBytes();
DatagramSocket socket = new DatagramSocket();
DatagramPakcet sendPacket = new DatagramPacket(dataToSend,dataToSend.length,address,port);
socket.send(sendPacket);
socket.close();
}
}
c.编程举例UDP如何接收数据
1
2
3
4
5
6
7
8
9
10
11
public class UDPServer{
public static void main(String[] args) throws Exception{
int port = 12345;
DatagramSocket socket = new DatagramSocket(port);
DatagramPacket inPacket = new DatagramPacket(new byte[256],256);
while(true){
socket.receive(inPacket);
System.out.println(inPacket.getData().toString());
}
}
}

2.理解线程同步的基础上,针对银行账户对象account,编程实现使用同步代码块和同步方法完成多线程正确取钱行为

1
2
3
4
5
6
7
8
9
10
11
// 同步代码块
public class DrawThread extends Thread{
public void run{
synchronized(account){
if(account.getBalance() >= drawAmount)
System.out.println("取钱成功");
else
System.out.println("取钱失败");
}
}
}
1
2
3
4
5
6
7
8
9
// 同步方法
public class Account{
public synchronized void draw(double drawAmount){
if(account.getBalance() >= drawAmount)
System.out.println("取钱成功");
else
System.out.println("取钱失败");
}
}

3.使用SQL语法和JDBC完成以下任务

1.创建学生表(字段:逻辑主键id,学号studentId,姓名studentName,年龄age,性别sex 0-女,1-男,并给studentName字段创建索引)
1
2
3
4
5
6
7
8
9
10
11
DROP DATABASE IF EXISTS studentdb;
CREATE DATABASE studentdb;
USE DATABASE studentdb;
CREATE TABLE student(
Id int primary key,
studentId int,
studentName varchar(20),
age int,
sex int COMMENT'0-女 1-男',
INDEX idx_stuName(studentName)
);
2.使用JDBC完成对数据库(IP-127.0.0.1,PORT-3306,DB-studentdb,用户名-student,密码-student)对student表的访问,输出全部学生的学号和姓名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JDBC{
public static void main(String[] args) throws Exception{
String URL = "jdbc:mysql://127.0.0.1:3306/studentdb?user=student&password=student";
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(URL);
stmt = con.createStatement();
rs = stmt.executeQuery("select * from student");
while(rs.next()){
System.out.println("学号" + rs.getString("studentId") + "姓名" + rs.getString("studentName"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
rs.close();
stm.close();
con.close();
}
}
}

4.使用ServerSocket和Socket类完成从客户端发送一行字符串给服务器,服务器接收后再发送同样一行字符串客户端(客户端发送double数据、文件、java对象给服务器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client{
public static void main(String[] args) throws Exception{
Socket socket = new Socket("127.0.0.1",30000);
// DOUBLE
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
dos.writeDouble(10.123);
dos.flush();
// OBJECT
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
oos.writeObject(new Student("studentName",20));
// FILE
byte[] buff = new byte[32];
BufferedInputStream bis = new BufferedInputeStream(new FileInputStream("\tx"));
bis.read(buff,0,buff.length);
OutputStream os = socket.getOutputStream();
os.write(buff,0,buff.length);
os.flush();socket.close;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Server{
public static void main(String[] args) throws Exception{
ServerSocket socket = new ServerSocket(30000);
while(true){
Socket s = socket.accept();
// DOUBLE
DataInputStream dis = new DataInputStream(new BufferedInputStream(s.getInputStream()));
Double d = dis.readDouble();
// OBJECT
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(s.getInputStream()));
try{
Student stu = (Student)ois.readObject();
System.out.println("姓名"+ stu.getName() + "年龄" + stu.getAge());
}catch(Exception e){
e.printStackTrace();
}
// FILE
byte[] buff = new byte[32];
InputStream is = s.getInputStream();
int byteRead = is.read(buff,0,buff.length);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tx"));
bos.write(buff,0,buff.length);

bos.flush();
bos.close();
s.close();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Server{
public static void main(String[] args){
try{
ServerSocket ss = new ServerSocket(30000);
Socket s = ss.accept();
BufferedReader br =new BufferedReader(new InputStreamReader(s.getInputStream()));
String msg = br.readLine();
System.out.println("客户端:" + msg);
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write("ehco" + msg);
bw.flush();
}catch (Exception e){
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client{
public static void main(String[] args){
try{
Socket s = new Socket("127.0.0.1",30000);
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write("测试消息文本");
BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream()));
String echo = br.readLine();
System.out.println("服务器:" + echo);
}catch(Exception e){
e.printStackTrace();
}
}
}

5.编写单例模式(Singleton)

1
2
3
4
5
6
7
public class Singleton{
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
1
2
3
4
5
6
7
8
public class Singleton{
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instance;
}
}

About Spring Data JPA

发表于 2018-07-01

About Spring Data JPA

1.Spring Data JPA

​ Spring Data JPA是Spring Data这一个大家族中的一部分,它使得实现基于JPA的存储(repository)变得更加容易。Spring Data JPA这个模块,增强支持了基于JPA的数据库访问层。Spring Data JPA构建的Spring-powered的驱动应用程序,使得数据访问变得更加简单。

2.Spring Data JPA基本功能

​ 实现应用程序的数据访问层,一直都是很麻烦的问题。为了执行简单的查询、分页和审计,需要编写太多冗余的模版代码。Spring Data JPA的出现,就是通过减少实际工作中的工作量来显著改进了数据访问层的实现。作为开发人员,编写repository接口,包括自定义的方法,Spring都自动提供了实现。

Features

  • Sophisticated support to build repositories based on Spring and JPA

  • 支持基于Spring和JPA构建数据库

  • Support for Querydsl predicates and thus type-safe JPA queries

  • 支持查询谓词,以及类安全的JPA查询

  • Transparent auditing of domain class

  • 域类的透明审计

  • Pagination support, dynamic query execution, ability to integrate custom data access code

  • 支持分页、动态查询,可以继承自定义数据库访问

  • Validation of @Query annotated queries at bootstrap time

  • 支持验证@Query的注释查询

  • Support for XML based entity mapping

  • 支持基于XML的实体映射

  • JavaConfig based repository configuration by introducing @EnableJpaRepositories.

  • 通过引入@EnableJpaRepositories,基于JavaConfig的repository的配置

3.核心接口

Repository接口 :

1
2
3
public interface Repository<T,ID extends Serializable>{

}

在这个接口中,定义了所有repository的类型。需要传入两个参数,一个是实体(采用泛型T),另一个则是实体中的ID的数据类型(Integer/String/…)

最近,在项目中,使用到的是JpaRepository。这里先讲讲JpaRepository

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface JpaRepository<T,ID extends Serializable> extends PagingAndSortingRepository<T,ID>{
List<T> findAll();

List<T> findAll(Sort sort);

<S extends T> List<S> save(Iterable<S> entities);

void flush();

T saveAndFlush(T entity);

void deleteInBatch(Iterable<T> entities);

void deleteAllInBatch();
}

其实,从方法名上基本都可以理解它们的功能和操作,只需调用即可。也可以进行重写和实现。传入需要的参数或者需要的返回值类型,见仁见智,根据实际情况编写不同的功能。

举个例子来说:

对于实体(Student)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Entity
@Data
@Table(name = "t_student")
@DynamicUpdate
public class Student{
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "gender")
private Integer gender;
@Column(name = "birth")
private Date birth;
...
/*
* 驼峰命名法
* @Column(name = "create_time")
* private String createTime;
*/
}

这里简单的举了一个例子,是学生的实体类,有一个基本的变量。为什么会有@Entity,@Data…诸如此类的注解?这是从hibernate就有的注解,主要的功能是为了将映射变得更加的容易。

下一篇就@Annotation的知识补充上

我们再把Student实体数据访问层的Repository补上

1
2
3
4
5
6
7
8
9
10
@Repository
public interface StudentRepository extends JpaRepository<Student, String>{
// 这里的两个参数 填写对应的实体类和实体ID字段的数据类型

// 举例说明 通过性别查找所有的男同学和女同学
List<Student> findAllStudentsByGender(Integer gender);

// 通过Pageable达到分页的查询效果
Page<Student> findAllStudentsByGender(Integer gender,Pageable pageable);
}

完成了数据访问层(Repository)之后,就可以编写Service和ServiceImpl的实现了。在Impl中只需将Repository注入(@AutoWired)即可完成对应的功能。

简单来说

​ JpaRepository简化了在SSM框架中,所需要的映射文件的配置:

​ 例如:spring-dao.xml,mapper/StudentDao.xml

SQL语句的编写:

​ 例如:

1
2
3
4
5
6
7
<mapper namespace="cn.inc.dao.StudentDao">
<select id="queryStudent" resultType="cn.inc.entity.Student">
SELECT *
FROM inc.t_student
ORDER BY id DESC
</select>
</mapper>

虽然说JpaRepository简化了很多在SSM框架中的映射,SQL编写,注入,组件等问题。但其实对于刚入门的我来说,在工作效率上提高了许多,但是感觉是走了很多的捷径去完成了工作而已,一些基础知识和底层的原理并没有深入的了解。这也是为什么要开启学习笔记的原因。

今天就更新到这里,感谢。

1…45

REX CHEN

日常记录

47 日志
20 标签
© 2019 REX CHEN