1mybatis高级原理分析

1mybatis高级原理分析

架构原理篇

架构图

认识Mybatis框架原理

框架作用:更加利用程序员进行开发使用

程序员使用持久层框架,就是完成CRUD操作

Java中的持久层框架,都是对JDBC进行的封装

Mybatis就是对JDBC以面向对象的思想进行拆分

mybatis基础详解

Mybatis基础详解

主键返回(mybatis的自增主键或者非自增主键)

批量查询

动态传参

查询缓存(一级缓存、二级缓存)

延迟加载(侵入式延迟加载、深度延迟加载)

关联查询(一对一、一对映射)

逆向工程

PageHelper分页插件

注解开发

Vue.js - Day1

课程介绍

前5天: 都在学习Vue基本的语法和概念;打包工具 Webpack , Gulp
后5天: 以项目驱动教学;

什么是Vue.js

  • Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架(React除了开发网站,还可以开发手机App, Vue语法也是可以用于进行手机App开发的,需要借助于Weex)

  • Vue.js 是前端的主流框架之一,和Angular.js、React.js 一起,并成为前端三大主流框架!

  • Vue.js 是一套构建用户界面的框架,只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。(Vue有配套的第三方类库,可以整合起来做大型项目的开发)

  • 前端的主要工作?主要负责MVC中的V这一层;主要工作就是和界面打交道,来制作前端页面效果;

为什么要学习流行框架

  • 企业为了提高开发效率:在企业中,时间就是效率,效率就是金钱;

    • 企业中,使用框架,能够提高开发的效率;
  • 提高开发效率的发展历程:原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js(能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念【通过框架提供的指令,我们前端程序员只需要关心数据的业务逻辑,不再关心DOM是如何渲染的了】)

  • 在Vue中,一个核心的概念,就是让用户不再操作DOM元素,解放了用户的双手,让程序员可以更多的时间去关注业务逻辑;

  • 增强自己就业时候的竞争力

    • 人无我有,人有我优
    • 你平时不忙的时候,都在干嘛?

框架和库的区别

  • 框架:是一套完整的解决方案;对项目的侵入性较大,项目如果需要更换框架,则需要重新架构整个项目。

    • node 中的 express;
  • 库(插件):提供某一个小功能,对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。

      1. 从Jquery 切换到 Zepto
      1. 从 EJS 切换到 art-template

Node(后端)中的 MVC 与 前端中的 MVVM 之间的区别

  • MVC 是后端的分层开发概念;
  • MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel
  • 为什么有了MVC还要有MVVM

Vue.js 基本代码 和 MVVM 之间的对应关系

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1. 导入Vue的包 -->
<script src="./lib/vue-2.4.0.js"></script>
</head>

<body>
<!-- 将来 new 的Vue实例,会控制这个 元素中的所有内容 -->
<!-- Vue 实例所控制的这个元素区域,就是我们的 V -->
<div id="app">
<p>{{ msg }}</p>
</div>

<script>
// 2. 创建一个Vue的实例
// 当我们导入包之后,在浏览器的内存中,就多了一个 Vue 构造函数
// 注意:我们 new 出来的这个 vm 对象,就是我们 MVVM中的 VM调度者
var vm = new Vue({
el: '#app', // 表示,当前我们 new 的这个 Vue 实例,要控制页面上的哪个区域
// 这里的 data 就是 MVVM中的 M,专门用来保存 每个页面的数据的
data: { // data 属性中,存放的是 el 中要用到的数据
msg: '欢迎学习Vue' // 通过 Vue 提供的指令,很方便的就能把数据渲染到页面上,程序员不再手动操作DOM元素了【前端的Vue之类的框架,不提倡我们去手动操作DOM元素了】
}
})
</script>
</body>

</html>

输出结果:

123

Vue之 - 基本的代码结构插值表达式v-cloak

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
[v-cloak]{
display: none;
}
</style>

</head>
<div id="app">
<!-- 使用 v-cloak 能够解决 插值表达式闪烁的问题 -->
<p v-cloak>{{msg}}</p>
</div>

<script src="./lib/vue-2.4.0.js"></script>
<script>
var vm=new Vue({
el:'#app',
data:{
msg:'123'
}
});
</script>
<body>
</body>
</html>
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
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
[v-cloak] {
/* display: none; */
}
</style>
</head>

<body>
<div id="app">
<!-- 使用 v-cloak 能够解决 插值表达式闪烁的问题 -->
<p v-cloak>++++++++ {{ msg }} ----------</p>
<h4 v-text="msg">==================</h4>
<!-- 默认 v-text 是没有闪烁问题的 -->
<!-- v-text会覆盖元素中原本的内容,但是插值表达式只会替换自己的这个占位符,不会把 整个元素的内容清空 -->

<div>{{msg2}}</div>
<div v-text="msg2"></div>
<div v-html="msg2">1212112</div>

<!-- v-bind: 是 Vue中,提供的用于绑定属性的指令 -->
<!-- <input type="button" value="按钮" v-bind:title="mytitle + '123'"> -->
<!-- 注意: v-bind: 指令可以被简写为 :要绑定的属性 -->
<!-- v-bind 中,可以写合法的JS表达式 -->

<!-- Vue 中提供了 v-on: 事件绑定机制 -->
<!-- <input type="button" value="按钮" :title="mytitle + '123'" v-on:click="alert('hello')"> -->


<input type="button" value="按钮" @click="show">
</div>


<script src="./lib/vue-2.4.0.js"></script>

<script>
var vm = new Vue({
el: '#app',
data: {
msg: '123',
msg2: '<h1>哈哈,我是一个大大的H1, 我大,我骄傲</h1>',
mytitle: '这是一个自己定义的title'
},
methods: { // 这个 methods属性中定义了当前Vue实例所有可用的方法
show: function () {
alert('Hello')
}
}
})


/* document.getElementById('btn').onclick = function(){
alert('Hello')
} */
</script>
</body>

</html>




<!-- 1. 如何定义一个基本的Vue代码结构 -->
<!-- 2. 插值表达式 和 v-text -->
<!-- 3. v-cloak -->
<!-- 4. v-html -->
<!-- 5. v-bind Vue提供的属性绑定机制 缩写是 : -->
<!-- 6. v-on Vue提供的事件绑定机制 缩写是 @ -->

Vue指令之v-textv-html

Vue指令之v-bind的三种用法

  1. 直接使用指令v-bind

  2. 使用简化指令:

  3. 在绑定的时候,拼接绑定内容::title="btnTitle + ', 这是追加的内容'"

Vue指令之v-on跑马灯效果

跑马灯效果

  1. HTML结构:
1
2
3
4
5
6
7
8
9
10

<div id="app">

<p>{{info}}</p>

<input type="button" value="开启" v-on:click="go">

<input type="button" value="停止" v-on:click="stop">

</div>
  1. Vue实例:
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

// 创建 Vue 实例,得到 ViewModel

var vm = new Vue({

el: '#app',

data: {

info: '猥琐发育,别浪~!',

intervalId: null

},

methods: {

go() {

// 如果当前有定时器在运行,则直接return

if (this.intervalId != null) {

return;

}

// 开始定时器

this.intervalId = setInterval(() => {

this.info = this.info.substring(1) + this.info.substring(0, 1);

}, 500);

},

stop() {

clearInterval(this.intervalId);

}

}

});

Vue指令之v-on的缩写事件修饰符

事件修饰符:

  • .stop 阻止冒泡

  • .prevent 阻止默认事件

  • .capture 添加事件侦听器时使用事件捕获模式

  • .self 只当事件在该元素本身(比如不是子元素)触发时触发回调

  • .once 事件只触发一次

Vue指令之v-model双向数据绑定

简易计算器案例

  1. HTML 代码结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

<div id="app">

<input type="text" v-model="n1">

<select v-model="opt">

<option value="0">+</option>

<option value="1">-</option>

<option value="2">*</option>

<option value="3">÷</option>

</select>

<input type="text" v-model="n2">

<input type="button" value="=" v-on:click="getResult">

<input type="text" v-model="result">

</div>
  1. Vue实例代码:
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

// 创建 Vue 实例,得到 ViewModel

var vm = new Vue({

el: '#app',

data: {

n1: 0,

n2: 0,

result: 0,

opt: '0'

},

methods: {

getResult() {

switch (this.opt) {

case '0':

this.result = parseInt(this.n1) + parseInt(this.n2);

break;

case '1':

this.result = parseInt(this.n1) - parseInt(this.n2);

break;

case '2':

this.result = parseInt(this.n1) * parseInt(this.n2);

break;

case '3':

this.result = parseInt(this.n1) / parseInt(this.n2);

break;

}

}

}

});

在Vue中使用样式

使用class样式

  1. 数组

    1
    <h1 :class="['red', 'thin']">这是一个邪恶的H1</h1>
  2. 数组中使用三元表达式

    1
    <h1 :class="['red', 'thin', isactive?'active':'']">这是一个邪恶的H1</h1>
  3. 数组中嵌套对象

    1
    <h1 :class="['red', 'thin', {'active': isactive}]">这是一个邪恶的H1</h1>
  4. 直接使用对象

    1
    <h1 :class="{red:true, italic:true, active:true, thin:true}">这是一个邪恶的H1</h1>

使用内联样式

  1. 直接在元素上通过 :style 的形式,书写样式对象

    1
    <h1 :style="{color: 'red', 'font-size': '40px'}">这是一个善良的H1</h1>
  2. 将样式对象,定义到 data 中,并直接引用到 :style

    • 在data上定义样式:

      1
      2
      3
      data: {
      h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200' }
      }
    • 在元素中,通过属性绑定的形式,将样式对象应用到元素中:

      1
      <h1 :style="h1StyleObj">这是一个善良的H1</h1>
  3. :style 中通过数组,引用多个 data 上的样式对象

    • 在data上定义样式:

      1
      2
      3
      4
      data: {
      h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200' },
      h1StyleObj2: { fontStyle: 'italic' }
      }
    • 在元素中,通过属性绑定的形式,将样式对象应用到元素中:

      1
      <h1 :style="[h1StyleObj, h1StyleObj2]">这是一个善良的H1</h1>

Vue指令之v-forkey属性

  1. 迭代数组
1
2
3
<ul>
<li v-for="(item, i) in list">索引:{{i}} --- 姓名:{{item.name}} --- 年龄:{{item.age}}</li>
</ul>
  1. 迭代对象中的属性
1
2
3
4

<!-- 循环遍历对象身上的属性 -->

<div v-for="(val, key, i) in userInfo">{{val}} --- {{key}} --- {{i}}</div>
  1. 迭代数字
1
2

<p v-for="i in 10">这是第 {{i}} 个P标签</p>

2.2.0+ 的版本里,当在组件中使用 v-for 时,key 现在是必须的。

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。

Vue指令之v-ifv-show

一般来说,v-if 有更高的切换消耗而 v-show 有更高的初始渲染消耗。因此,如果需要频繁切换 v-show 较好,如果在运行时条件不大可能改变 v-if 较好。

品牌管理案例

添加新品牌

删除品牌

根据条件筛选品牌

  1. 1.x 版本中的filterBy指令,在2.x中已经被废除:

filterBy - 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<tr v-for="item in list | filterBy searchName in 'name'">

<td>{{item.id}}</td>

<td>{{item.name}}</td>

<td>{{item.ctime}}</td>

<td>

<a href="#" @click.prevent="del(item.id)">删除</a>

</td>

</tr>
  1. 在2.x版本中手动实现筛选的方式
  • 筛选框绑定到 VM 实例中的 searchName 属性:
1
2
3
4

<hr> 输入筛选名称:

<input type="text" v-model="searchName">
  • 在使用 v-for 指令循环每一行数据的时候,不再直接 item in list,而是 in 一个 过滤的methods 方法,同时,把过滤条件searchName传递进去:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<tbody>

<tr v-for="item in search(searchName)">

<td>{{item.id}}</td>

<td>{{item.name}}</td>

<td>{{item.ctime}}</td>

<td>

<a href="#" @click.prevent="del(item.id)">删除</a>

</td>

</tr>

</tbody>
  • search 过滤方法中,使用 数组的 filter 方法进行过滤:
1
2
3
4
5
6
7
8
9
10

search(name) {

return this.list.filter(x => {

return x.name.indexOf(name) != -1;

});

}

Vue调试工具vue-devtools的安装步骤和使用

Vue.js devtools - 翻墙安装方式 - 推荐

过滤器

概念:Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache 插值和 v-bind 表达式。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示;

私有过滤器

  1. HTML元素:
1
2

<td>{{item.ctime | dataFormat('yyyy-mm-dd')}}</td>
  1. 私有 filters 定义方式:
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

filters: { // 私有局部过滤器,只能在 当前 VM 对象所控制的 View 区域进行使用

dataFormat(input, pattern = "") { // 在参数列表中 通过 pattern="" 来指定形参默认值,防止报错

var dt = new Date(input);

// 获取年月日

var y = dt.getFullYear();

var m = (dt.getMonth() + 1).toString().padStart(2, '0');

var d = dt.getDate().toString().padStart(2, '0');



// 如果 传递进来的字符串类型,转为小写之后,等于 yyyy-mm-dd,那么就返回 年-月-日

// 否则,就返回 年-月-日 时:分:秒

if (pattern.toLowerCase() === 'yyyy-mm-dd') {

return `${y}-${m}-${d}`;

} else {

// 获取时分秒

var hh = dt.getHours().toString().padStart(2, '0');

var mm = dt.getMinutes().toString().padStart(2, '0');

var ss = dt.getSeconds().toString().padStart(2, '0');



return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;

}

}

}

使用ES6中的字符串新方法 String.prototype.padStart(maxLength, fillString=’’) 或 String.prototype.padEnd(maxLength, fillString=’’)来填充字符串;

全局过滤器

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

// 定义一个全局过滤器

Vue.filter('dataFormat', function (input, pattern = '') {

var dt = new Date(input);

// 获取年月日

var y = dt.getFullYear();

var m = (dt.getMonth() + 1).toString().padStart(2, '0');

var d = dt.getDate().toString().padStart(2, '0');



// 如果 传递进来的字符串类型,转为小写之后,等于 yyyy-mm-dd,那么就返回 年-月-日

// 否则,就返回 年-月-日 时:分:秒

if (pattern.toLowerCase() === 'yyyy-mm-dd') {

return `${y}-${m}-${d}`;

} else {

// 获取时分秒

var hh = dt.getHours().toString().padStart(2, '0');

var mm = dt.getMinutes().toString().padStart(2, '0');

var ss = dt.getSeconds().toString().padStart(2, '0');



return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;

}

});

注意:当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用!

键盘修饰符以及自定义键盘修饰符

1.x中自定义键盘修饰符【了解即可】

1
2

Vue.directive('on').keyCodes.f2 = 113;

2.x中自定义键盘修饰符

  1. 通过Vue.config.keyCodes.名称 = 按键值来自定义案件修饰符的别名:
1
2

Vue.config.keyCodes.f2 = 113;
  1. 使用自定义的按键修饰符:
1
2

<input type="text" v-model="name" @keyup.f2="add">

自定义指令

  1. 自定义全局和局部的 自定义指令:
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

// 自定义全局指令 v-focus,为绑定的元素自动获取焦点:

Vue.directive('focus', {

inserted: function (el) { // inserted 表示被绑定元素插入父节点时调用

el.focus();

}

});



// 自定义局部指令 v-color 和 v-font-weight,为绑定的元素设置指定的字体颜色 和 字体粗细:

directives: {

color: { // 为元素设置指定的字体颜色

bind(el, binding) {

el.style.color = binding.value;

}

},

'font-weight': function (el, binding2) { // 自定义指令的简写形式,等同于定义了 bind 和 update 两个钩子函数

el.style.fontWeight = binding2.value;

}

}
  1. 自定义指令的使用方式:
1
2

<input type="text" v-model="searchName" v-focus v-color="'red'" v-font-weight="900">

Vue 1.x 中 自定义元素指令【已废弃,了解即可】

1
2
3
4
5
Vue.elementDirective('red-color', {
bind: function () {
this.el.style.color = 'red';
}
});

使用方式:

1
<red-color>1232</red-color>

相关文章

  1. vue.js 1.x 文档
  2. vue.js 2.x 文档
  3. String.prototype.padStart(maxLength, fillString)
  4. js 里面的键盘事件对应的键码
  5. Vue.js双向绑定的实现原理

简历的用户是谁

简历撰写的几个框架

如何数据化和结果导向

简历撰写的几个坑

掌握:简历的撰写方法

  1. 让HR注意到
  2. 让业务部门能够有第一印象
  3. 让面试官有符合你预期的面试方向

应用:写出一份高质量简历

简历的用户是谁

HR:并没有那么懂业务,更不懂专业术语

—-少传递概念,多说岗位的关键字,强调匹配性

业务部门的筛选:你的经验有多少可复用性

—-根据JD和其他信息推测对方的业务,对过往的经历进行筛选,找到交集,优先写出

面试官:有哪些经验可以拿来仔细询问可复用性

—-需要为面试考虑,简历会引导面试官的问题

简历撰写的几个框架

千万不要以为一份简历可以应用于所有的职位和公司

简历的前置信息收集至少是目标岗位的JD

需要重温STAR原则,但是需要落实到实战

最重要的原则是说结果,讲产出

思考简历书写的STAR原则

S:情景(15%)要对场景有简单的描述,不用传递专业词语

T:任务(15%)数据验收指标

A:行动(20%)用什么方法达到什么样的指标,为什么要做,遇到的问题,如何解决

R:结果(50%)产品最终的数据结果,是否成功的完成任务,最终完成多少,产品的价值在哪里

关于背景和任务,一定要把用户当做小白,用最简单通俗的方式说清楚,讲明白

关于做法和结果,如果有好的结果,当然要写,但是不一定所有的结果都是好的,关于一般的结果如果有反思则写上,也未必减分

如何数据化和结果导向

简历中的结果是什么?

大的原则:有数据说数据,没有数据说影响,没有影响说评价

SpringDataJPA

SpringDataJPA

ORM概述

JDBC优化

建立实体类和表的关系

建立实体类中属性和表中字段的关系

ORM理解

ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM对象关系映射

简单的说:ORM就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。

ORM思想

主要目的:操作实体类就相当于操作数据库表

建立两个映射关系:实体类和表的映射关系,实体类中属性和表中字段的映射关系

此时不再重点关注sql语句的编写

实现了ORM思想的框架:mybatis,hibernate

为什么使用ORM

当实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存数据、修改数据、删除数据,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM),主要实现程序对象到关系数据库数据的映射。

常见ORM框架

常见的orm框架:Mybatis(ibatis)、Hibernate、Jpa

hibernate与JPA的概述

JPA的供应商

JPA的目标之一是制定一个可以由很多供应商实现的API,目前Hibernate 3.2+、TopLink 10.1+以及OpenJPA都提供了JPA的实现

Hibernate

-JPA的始作俑者就是Hibernate的作者

-Hibernate从3.2开始兼容JPA

OpenJPA

-OpenJPA是Apache组织提供的开源项目

TopLink

-TopLink以前需要收费,如今开源

hibernate概述

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

JPA规范概述

JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。注意JPA是一种规范

JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

JPA规范不去执行代码,真正执行代码是依托于hibernate、toplink等的实现方式

JPA的优势

1.标准化

JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。

2.容器级特性的支持

JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。

3.简单方便

JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成

4.查询能力

JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。

5.高级特性

JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。

JPA与hibernate的关系

JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服务厂商来提供实现。

JPA是hibernate的一个抽象,就像JDBC和JDBC驱动的关系

JPA是规范:JPA本质上就是一种ORM框架,因为JPA并未提供ORM实现,它只是制定了一些规范,提供了一些编程的API接口,但是具体实现则由ORM厂商提供实现

Hibernate是实现:Hibernate除了作为ORM框架之外,它也是一种JPA实现

从功能上来说,JPA是Hibernate功能的一个子集

img

JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。

JPA详解

JPA全称Java Persistence API

用于对象持久化的API,JavaEE5.0平台标准的ORM规范,使得应用程序以统一的方式访问持久层

JPA的入门案例

需求介绍

本章节我们是实现的功能是保存一个客户到数据库的客户表中。

创建数据库

创建数据库名为jpa

创建cst_customer表

1
2
3
4
5
6
7
8
9
10
11
/*创建客户表*/
CREATE TABLE cst_customer (
cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
cust_name varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
cust_source varchar(32) DEFAULT NULL COMMENT '客户信息来源',
cust_industry varchar(32) DEFAULT NULL COMMENT '客户所属行业',
cust_level varchar(32) DEFAULT NULL COMMENT '客户级别',
cust_address varchar(128) DEFAULT NULL COMMENT '客户联系地址',
cust_phone varchar(64) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

搭建环境

搭建环境过程

  1. 创建maven工程导入坐标

  2. 需要配置jpa的核心配置文件
    位置:配置到类路径下的一个叫做 META-INF 的文件夹下

    命名:persistence.xml

    image-20200505110838977

  3. 编写客户的实体类

  4. 配置实体类和表,类中属性和表中字段的映射关系

  5. 保存客户到数据库中

创建maven工程导入坐标

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
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.hibernate.version>5.0.7.Final</project.hibernate.version>
</properties>

<dependencies>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- hibernate对jpa的支持包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${project.hibernate.version}</version>
</dependency>

<!-- c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${project.hibernate.version}</version>

</dependency>

<!-- log日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<!-- Mysql and MariaDB -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>

jpa的核心配置文件

image-20200501175411156

复制文件模板

image-20200501181249845

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
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!--配置持久化单元
name:持久化单元名称
transaction-type:事务类型包含以下两个
RESOURCE_LOCAL:本地事务管理
JTA:分布式事务管理 -->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<!--配置JPA规范的服务提供商 即jpa的实现方式-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

<properties>
<!-- 数据库驱动 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<!-- 数据库地址 -->
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" />
<!-- 数据库用户名 -->
<property name="javax.persistence.jdbc.user" value="root" />
<!-- 数据库密码 -->
<property name="javax.persistence.jdbc.password" value="root" />

<!--jpa提供者的可选配置:我们的JPA规范的提供者为hibernate,所以jpa的核心配置中兼容hibernate的配置 -->
<!--是否显示sql false|true-->
<property name="hibernate.show_sql" value="true" />
<!--是否格式化sql语句-->
<property name="hibernate.format_sql" value="true" />
<!--
自动创建数据库表:hibernate.hbm2ddl.auto
create:程序运行时创建数据库表(如果有表,先删除再创建)
update:程序运行时创建表(如果有表,则不会创建表)
none:不会创建表
-->
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>

编写客户的实体类

在实体类上使用JPA注解的形式配置映射关系

有的注解都是使用JPA的规范提供的注解,所以在导入注解包的时候,一定要导入javax.persistence下的

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* 客户的实体类
* 配置映射关系
*
*
* 1.实体类和表的映射关系
* @Entity:声明实体类
* @Table : 配置实体类和表的映射关系
* name : 配置数据库表的名称
* 2.实体类中属性和表中字段的映射关系
*
*
*/
@Entity
@Table(name = "cst_customer")
public class Customer {

/**
* @Id:声明主键的配置
* @GeneratedValue:配置主键的生成策略
* strategy
* GenerationType.IDENTITY :自增,mysql
* * 使用它的条件是底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
* GenerationType.SEQUENCE : 序列,oracle
* * 使用它的条件是底层数据库必须支持序列
* GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
* GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
* 推荐使用IDENTITY和SEQUENCE
* @Column:配置属性和字段的映射关系
* name:数据库表中字段的名称
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId; //客户的主键

@Column(name = "cust_name")
private String custName;//客户名称

@Column(name="cust_source")
private String custSource;//客户来源

@Column(name="cust_level")
private String custLevel;//客户级别

@Column(name="cust_industry")
private String custIndustry;//客户所属行业

@Column(name="cust_phone")
private String custPhone;//客户的联系方式

@Column(name="cust_address")
private String custAddress;//客户地址

public Long getCustId() {
return custId;
}

public void setCustId(Long custId) {
this.custId = custId;
}

public String getCustName() {
return custName;
}

public void setCustName(String custName) {
this.custName = custName;
}

public String getCustSource() {
return custSource;
}

public void setCustSource(String custSource) {
this.custSource = custSource;
}

public String getCustLevel() {
return custLevel;
}

public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}

public String getCustIndustry() {
return custIndustry;
}

public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}

public String getCustPhone() {
return custPhone;
}

public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}

public String getCustAddress() {
return custAddress;
}

public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}

@Override
public String toString() {
return "Customer{" +
"custId=" + custId +
", custName='" + custName + '\'' +
", custSource='" + custSource + '\'' +
", custLevel='" + custLevel + '\'' +
", custIndustry='" + custIndustry + '\'' +
", custPhone='" + custPhone + '\'' +
", custAddress='" + custAddress + '\'' +
'}';
}
}

常用注解的说明

@Entity

作用:

指定当前类是实体类,将映射到指定的数据库表

@Table

作用:

指定实体类和表之间的对应关系。当实体类与其映射的数据库表名不同名时需要使用@Table标注说明,该标注与@Entity标注并列使用,置于实体类声明语句之前

属性:

name:

指定数据库表的名称

@Id

作用:

指定当前字段是主键。

@GeneratedValue

作用:

指定主键的生成方式。

属性:

strategy :指定主键生成策略。

@Column

作用:

指定实体类属性和数据库表之间的对应关系,当实体的属性与其映射的数据库表的列不同名时需要使用@Column标注说明

属性:

name:指定数据库表的列名称。

unique:是否唯一

nullable:是否可以为空

length:设置字段长度

@Temporal

在核心的Java API中并没有定义Date类型的精度(temproal percision)

而在JPA中表示Date类型的数据有DATE、TIME和TIMESTAMP三种精度,即单纯的日期、时间或者两者兼备,在进行属性映射时可以使用@Temporal注解来调整精度

1
2
3
4
5
6
7
8
9
10
@Temporal(TemporalType.DATE)
private Date date;

public Date getDate() {
return date;
}

public void setDate(Date date) {
this.date = date;
}

image-20200505164347411

案例实现保存操作

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
public class JpaTest {

/**
* 测试jpa的保存
* 案例:保存一个客户到数据库中
* Jpa的操作步骤
* 1.加载配置文件创建工厂(实体管理器工厂)对象
* 2.通过实体管理器工厂获取实体管理器
* 3.获取事务对象,开启事务
* 4.完成增删改查操作
* 5.提交事务(回滚事务)
* 6.释放资源
*/
@Test
public void testSave() {

//1.加载配置文件并创建工厂(实体管理器工厂)对象,myJpa在配置文件中
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
//2.通过实体管理器工厂获取实体管理器
EntityManager em = factory.createEntityManager();

//3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction(); //获取事务对象
tx.begin();//开启事务
//4.完成增删改查操作:保存一个客户到数据库中
Customer customer = new Customer();
customer.setCustName("传智播客");
customer.setCustIndustry("教育");
//保存,
em.persist(customer); //保存操作
//5.提交事务
tx.commit();
//6.释放资源
em.close();
factory.close();

}

}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Hibernate: 
drop table if exists cst_customer
Hibernate:
create table cst_customer (
cust_id bigint not null auto_increment,
cust_address varchar(255),
cust_industry varchar(255),
cust_level varchar(255),
cust_name varchar(255),
cust_phone varchar(255),
cust_source varchar(255),
primary key (cust_id)
)
Hibernate:
insert
into
cst_customer
(cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source)
values
(?, ?, ?, ?, ?, ?)

image-20200502080423978

JPA的API介绍

1.加载配置文件创建实体管理器工厂

Persisitence:静态方法(根据持久化单元名称创建实体管理器工厂)

createEntityMnagerFactory(持久化单元名称),作用:创建实体管理器工厂

Persistence对象主要作用是用于获取EntityManagerFactory对象的 。通过调用该类的

createEntityManagerFactory静态方法,根据配置文件中持久化单元名称创建EntityManagerFactory

1
2
3
4
//1. 创建 EntitymanagerFactory
@Test
String unitName = "myJpa";
EntityManagerFactory factory= Persistence.createEntityManagerFactory(unitName);

2.根据实体管理器工厂,创建实体管理器

EntityManagerFactory 接口主要用来创建 EntityManager 实例

1
2
//创建实体管理类
EntityManager em = factory.createEntityManager();

EntityManagerFactory :获取EntityManager对象

方法:createEntityManager

createEntityManager方法内部维护的很多的内容,即内部维护了数据库信息,维护了缓存信息,维护了所有的实体管理器对象,再创建EntityManagerFactory的过程中会根据配置创建数据库表

EntityManagerFactory的创建过程比较浪费资源

特点:线程安全的对象,多个线程访问同一个EntityManagerFactory不会有线程安全问题

如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
思路:创建一个公共的EntityManagerFactory的对象,即以静态代码块的形式创建EntityManagerFactory

3.创建事务对象,开启事务

EntityManager对象:实体类管理器

在 JPA 规范中, EntityManager是完成持久化操作的核心对象。实体类作为普通 java对象,只有在调用 EntityManager将其持久化后才会变成持久化对象。EntityManager对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。

我们可以通过调用EntityManager的方法完成获取事务,以及持久化数据库的操作

​ beginTransaction : 创建事务对象

​ presist : 保存

​ merge : 更新

​ remove : 删除

​ find/getRefrence : 根据id查询

EntityTransaction

事务在操作的过程中用到的对象Transaction 对象

在 JPA 规范中, EntityTransaction是完成事务操作的核心对象,对于EntityTransaction在我们的java代码中承接的功能比较简单

Transaction 对象 : 事务

​ begin:开启事务

​ commit:提交事务

​ rollback:回滚

4.增删改查操作

5.提交事务

6.释放资源

JPA中的主键生成策略

通过annotation(注解)来映射hibernate实体的,基于annotation的hibernate主键标识为@Id, 其生成规则由@GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法。

JPA提供的四种标准用法为TABLE、SEQUENCE、IDENTITY、AUTO

具体说明如下:

IDENTITY

主键由数据库自动生成(主要是自动增长型)

用法:

1
2
3
@Id  
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long custId;

SEQUENCE

根据底层数据库的序列来生成主键,条件是数据库支持序列。

用法:

1
2
3
4
@Id  
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq")
@SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")
private Long custId;
1
2
3
4
5
6
7
8
9
10
11
12
13
//@SequenceGenerator源码中的定义
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface SequenceGenerator {
//表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中
String name();
//属性表示生成策略用到的数据库序列名称。
String sequenceName() default "";
//表示主键初识值,默认为0
int initialValue() default 0;
//表示每次主键值增加的大小,例如设置1,则表示每次插入新记录后自动加1,默认为50
int allocationSize() default 50;
}

AUTO

主键由程序控制,由程序帮助我们选择最佳的主键生成策略

用法:

1
2
3
@Id  
@GeneratedValue(strategy = GenerationType.AUTO)
private Long custId;

TABLE

使用一个特定的数据库表格来保存主键,将当前主键的值单独保存到一个数据库的表中,主键的值每次都是从指定的表中查询来获得的

这种方法生成主键的策略可以适用于任何数据库,不必担心不同数据库兼容造成的问题

用法

1
2
3
4
5
6
7
8
9
10
@Id  
@GeneratedValue(strategy = GenerationType.TABLE, generator="ID_GENERATOR")
@TableGenerator(name = "ID_GENERATOR",
table="tb_generator",
pkColumnName="PK_NAME",
pkColumnValue="CUSTOMER_ID",
valueColumnName="PK_VALUE",
allocationSize=100
)
private Long custId;

pkColumnName和pkColumnValue来唯一确定一行,valueColumnName来唯一确定一列

image-20200505170028419

1
2
3
4
5
6
7
//这里应用表tb_generator,定义为 :
CREATE TABLE tb_generator (
ID INT NOT NULL,
PK_NAME VARCHAR(255) NOT NULL,
PK_VALUE INT NOT NULL,
PRIMARY KEY(id)
)

插入数据

输出结果

image-20200505174537675

@TableGenerator的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//@TableGenerator的定义:
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface TableGenerator {
//表示该表主键生成策略的名称
String name();

//表示表生成策略所持久化的表名
String table() default "";

//属性的值表示在持久化表中,该主键生成策略所对应键值的名称。
String pkColumnName() default "";

//属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。
String valueColumnName() default "";

//属性的值表示在持久化表中,该生成策略所对应的主键。
String pkColumnValue() default "";

//表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。
int allocationSize() default 50;
}

image-20200505174743901

抽取JPAUtil工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 解决实体管理器工厂的浪费资源和耗时问题
* 通过静态代码块的形式,当程序第一次访问此工具类时,创建一个公共的实体管理器工厂对象
*
* 第一次访问getEntityManager方法:经过静态代码块创建一个factory对象,再调用方法创建一个EntityManager对象
* 第二次方法getEntityManager方法:直接通过一个已经创建好的factory对象,创建EntityManager对象
*/
public class JpaUtils {

private static EntityManagerFactory factory;

static {
//1.加载配置文件,创建entityManagerFactory
// 注意:该方法参数必须和persistence.xml中persistence-unit标签name属性取值一致
factory = Persistence.createEntityManagerFactory("myJpa");
}

/**
* 获取EntityManager对象
*/
public static EntityManager getEntityManager() {
return factory.createEntityManager();
}
}

测试代码

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
@Test
public void testSave() {

// //1.加载配置文件并创建工厂(实体管理器工厂)对象,myJpa在配置文件中
// EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
// //2.通过实体管理器工厂获取实体管理器
// EntityManager em = factory.createEntityManager();

EntityManager em = JpaUtils.getEntityManager();
//3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction(); //获取事务对象
tx.begin();//开启事务
//4.完成增删改查操作:保存一个客户到数据库中
Customer customer = new Customer();
customer.setCustName("传智播客");
customer.setCustIndustry("教育");
//保存,
em.persist(customer); //保存操作
//5.提交事务
tx.commit();
//6.释放资源
em.close();
//不需要关闭工厂,因为工厂的公共的,如果关闭,其他线程就不能再次使用
//factory.close();

}

}

使用JPA完成增删改查操作

根据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
/**
* 根据id查询客户
* 使用find方法查询:
* 1.查询的对象就是当前客户对象本身
* 2.在调用find方法的时候,就会发送sql语句查询数据库
*
* 使用find会进行立即加载
*
*
*/
@Test
public void testFind() {
//1.通过工具类获取entityManager
EntityManager entityManager = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
//3.增删改查 -- 根据id查询客户
/**
* find : 根据id查询数据
* class:查询数据的结果需要包装的实体类类型的字节码
* id:查询的主键的取值
*/
Customer customer = entityManager.find(Customer.class, 1l);
// System.out.print(customer);
//4.提交事务
tx.commit();
//5.释放资源
entityManager.close();
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Hibernate: 
select
customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_address as cust_add2_0_0_,
customer0_.cust_industry as cust_ind3_0_0_,
customer0_.cust_level as cust_lev4_0_0_,
customer0_.cust_name as cust_nam5_0_0_,
customer0_.cust_phone as cust_pho6_0_0_,
customer0_.cust_source as cust_sou7_0_0_
from
cst_customer customer0_
where
customer0_.cust_id=?
Customer{custId=1, custName='传智播客', custSource='null', custLevel='null', custIndustry='教育', custPhone='null', custAddress='null'}

延迟加载

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
/**
* 根据id查询客户
* getReference方法
* 1.获取的对象是一个动态代理对象
* 2.调用getReference方法不会立即发送sql语句查询数据库
* * 当调用查询结果对象的时候,才会发送查询的sql语句:什么时候用,什么时候发送sql语句查询数据库
*
* 延迟加载(懒加载)
* * 得到的是一个动态代理对象
* * 什么时候用,什么使用才会查询
*/
@Test
public void testReference() {
//1.通过工具类获取entityManager
EntityManager entityManager = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
//3.增删改查 -- 根据id查询客户
/**
* getReference : 根据id查询数据
* class:查询数据的结果需要包装的实体类类型的字节码
* id:查询的主键的取值
*/
Customer customer = entityManager.getReference(Customer.class, 1l);
System.out.print(customer);
//4.提交事务
tx.commit();
//5.释放资源
entityManager.close();
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Hibernate: 
select
customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_address as cust_add2_0_0_,
customer0_.cust_industry as cust_ind3_0_0_,
customer0_.cust_level as cust_lev4_0_0_,
customer0_.cust_name as cust_nam5_0_0_,
customer0_.cust_phone as cust_pho6_0_0_,
customer0_.cust_source as cust_sou7_0_0_
from
cst_customer customer0_
where
customer0_.cust_id=?
Customer{custId=1, custName='传智播客', custSource='null', custLevel='null', custIndustry='教育', custPhone='null', custAddress='null'}

更新操作

merge操作,如果修改中包含id,则是修改更新操作,如果不包含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
/**
* 更新客户的操作
* merge(Object)
*/
@Test
public void testUpdate() {
//1.通过工具类获取entityManager
EntityManager entityManager = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
//3.增删改查 -- 更新操作

//i 查询客户
Customer customer = entityManager.find(Customer.class,1l);
//ii 更新客户
customer.setCustIndustry("it教育");
entityManager.merge(customer);

//4.提交事务
tx.commit();
//5.释放资源
entityManager.close();
}

输出结果

image-20200505113844968

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 删除客户的案例
*
*/
@Test
public void testRemove() {
//1.通过工具类获取entityManager
EntityManager entityManager = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
//3.增删改查 -- 删除客户

//i 根据id查询客户
Customer customer = entityManager.find(Customer.class,1l);
//ii 调用remove方法完成删除操作
entityManager.remove(customer);

//4.提交事务
tx.commit();
//5.释放资源
entityManager.close();
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Hibernate: 
select
customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_address as cust_add2_0_0_,
customer0_.cust_industry as cust_ind3_0_0_,
customer0_.cust_level as cust_lev4_0_0_,
customer0_.cust_name as cust_nam5_0_0_,
customer0_.cust_phone as cust_pho6_0_0_,
customer0_.cust_source as cust_sou7_0_0_
from
cst_customer customer0_
where
customer0_.cust_id=?
Hibernate:
delete
from
cst_customer
where
cust_id=?

保存与增加

不要忘记将hibernate.hbm2ddl.auto改成update

若对象有id,则不能执行insert操作,并抛出异常

1
2
3
4
5
6
7
<!--
自动创建数据库表:hibernate.hbm2ddl.auto
create:程序运行时创建数据库表(如果有表,先删除再创建)
update:程序运行时创建表(如果有表,则不会创建表)
none:不会创建表
-->
<property name="hibernate.hbm2ddl.auto" value="update" />
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
@Test
public void testSave() {

////1.加载配置文件并创建工厂(实体管理器工厂)对象,myJpa在配置文件中
//EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
////2.通过实体管理器工厂获取实体管理器
//EntityManager em = factory.createEntityManager();

EntityManager em = JpaUtils.getEntityManager();
//3.获取事务对象,开启事务
EntityTransaction tx = em.getTransaction(); //获取事务对象
tx.begin();//开启事务
//4.完成增删改查操作:保存一个客户到数据库中
Customer customer = new Customer();
customer.setCustName("传智播客");
customer.setCustIndustry("教育");
//保存,
em.persist(customer); //保存操作
//5.提交事务
tx.commit();
//6.释放资源
em.close();
//不需要关闭工厂,因为工厂的公共的,如果关闭,其他线程就不能再次使用
//factory.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
30
31
32
33
34
35
/**
* 保存一个实体
*/
@Test
public void testAdd() {
// 定义对象
Customer c = new Customer();
c.setCustName("传智学院");
c.setCustLevel("VIP客户");
c.setCustSource("网络");
c.setCustIndustry("IT教育");
c.setCustAddress("昌平区北七家镇");
c.setCustPhone("010-84389340");
EntityManager em = null;
EntityTransaction tx = null;
try {
// 获取实体管理对象
em = JPAUtil.getEntityManager();
// 获取事务对象
tx = em.getTransaction();
// 开启事务
tx.begin();
// 执行操作
em.persist(c);
// 提交事务
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.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
@Test
public void testMerge(){
//定义对象
EntityManager em=null;
EntityTransaction tx=null;
try{
//获取实体管理对象
em=JPAUtil.getEntityManager();
//获取事务对象
tx=em.getTransaction();
//开启事务
tx.begin();
//执行操作
Customer c1 = em.find(Customer.class, 6L);
c1.setCustName("江苏传智学院");
em.clear();//把c1对象从缓存中清除出去
em.merge(c1);
//提交事务
tx.commit();
}catch(Exception e){
//回滚事务
tx.rollback();
e.printStackTrace();
}finally{
//释放资源
em.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
/**
* 删除
*/
@Test
public void testRemove() {
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
// 获取实体管理对象
em = JPAUtil.getEntityManager();
// 获取事务对象
tx = em.getTransaction();
// 开启事务
tx.begin();
// 执行操作
Customer c1 = em.find(Customer.class, 6L);
em.remove(c1);
// 提交事务
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.close();
}
}

根据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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* 查询一个: 使用立即加载的策略
*/
@Test
public void testGetOne() {
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
// 获取实体管理对象
em = JPAUtil.getEntityManager();
// 获取事务对象
tx = em.getTransaction();
// 开启事务
tx.begin();
// 执行操作
Customer c1 = em.find(Customer.class, 1L);
// 提交事务
tx.commit();
System.out.println(c1); // 输出查询对象
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.close();
}
}

// 查询实体的缓存问题
@Test
public void testGetOne() {
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
// 获取实体管理对象
em = JPAUtil.getEntityManager();
// 获取事务对象
tx = em.getTransaction();
// 开启事务
tx.begin();
// 执行操作
Customer c1 = em.find(Customer.class, 1L);
Customer c2 = em.find(Customer.class, 1L);
System.out.println(c1 == c2);// 输出结果是true,EntityManager也有缓存
// 提交事务
tx.commit();
System.out.println(c1);
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.close();
}
}

// 延迟加载策略的方法:
/**
* 查询一个: 使用延迟加载策略
*/
@Test
public void testLoadOne() {
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
// 获取实体管理对象
em = JPAUtil.getEntityManager();
// 获取事务对象
tx = em.getTransaction();
// 开启事务
tx.begin();
// 执行操作
Customer c1 = em.getReference(Customer.class, 1L);
// 提交事务
tx.commit();
System.out.println(c1);
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.close();
}
}

JPQL

JPA中的复杂查询

JPQL全称Java Persistence Query Language

基于首次在EJB2.0中引入的EJB查询语言(EJB QL),Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起·使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。

其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性。

sql:查询的是表和表中的字段

jpql:查询的是实体类和类中的属性

查询全部

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
/**
* 查询全部
* jqpl:from cn.itcase.domain.Customer
* sql:SELECT * FROM cst_customer
*/
@Test
public void testFindAll() {
//1.获取entityManager对象
EntityManager em = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.查询全部
String jpql = "from Customer ";
Query query = em.createQuery(jpql);//创建Query查询对象,query对象才是执行jqpl的对象

//发送查询,并封装结果集
List list = query.getResultList();

for (Object obj : list) {
System.out.print(obj);
}

//4.提交事务
tx.commit();
//5.释放资源
em.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
30
31
32
33
/**
* 排序查询: 倒序查询全部客户(根据id倒序)
* sql:SELECT * FROM cst_customer ORDER BY cust_id DESC
* jpql:from Customer order by custId desc
*
* 进行jpql查询
* 1.创建query查询对象
* 2.对参数进行赋值
* 3.查询,并得到返回结果
*/
@Test
public void testOrders() {
//1.获取entityManager对象
EntityManager em = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.查询全部
String jpql = "from Customer order by custId desc";
Query query = em.createQuery(jpql);//创建Query查询对象,query对象才是执行jqpl的对象

//发送查询,并封装结果集
List list = query.getResultList();

for (Object obj : list) {
System.out.println(obj);
}

//4.提交事务
tx.commit();
//5.释放资源
em.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
30
31
32
/**
* 使用jpql查询,统计客户的总数
* sql:SELECT COUNT(cust_id) FROM cst_customer
* jpql:select count(custId) from Customer
*/
@Test
public void testCount() {
//1.获取entityManager对象
EntityManager em = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.查询全部
//i.根据jpql语句创建Query查询对象
String jpql = "select count(custId) from Customer";
Query query = em.createQuery(jpql);
//ii.对参数赋值
//iii.发送查询,并封装结果

/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
Object result = query.getSingleResult();

System.out.println(result);

//4.提交事务
tx.commit();
//5.释放资源
em.close();
}

注:jpql不可以写select *,但是可以写select count()select 属性等等

count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL

count(列名)只包括列名那一列,在统计结果的时候,会忽略列名值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计

分页查询

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
/**
* 分页查询
* sql:select * from cst_customer limit 0,2
* jqpl : from Customer
*/
@Test
public void testPaged() {
//1.获取entityManager对象
EntityManager em = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.查询全部
//i.根据jpql语句创建Query查询对象
String jpql = "from Customer";
Query query = em.createQuery(jpql);
//ii.对参数赋值 -- 分页参数
//起始索引,即每次从0开始查,但不包含0
query.setFirstResult(0);
//每页查询的条数
query.setMaxResults(2);

//iii.发送查询,并封装结果

/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
List list = query.getResultList();

for(Object obj : list) {
System.out.println(obj);
}

//4.提交事务
tx.commit();
//5.释放资源
em.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
30
31
32
33
34
35
36
37
38
/**
* 条件查询
* 案例:查询客户名称以‘传智播客’开头的客户
* sql:SELECT * FROM cst_customer WHERE cust_name LIKE ?
* jpql : from Customer where custName like ?
*/
@Test
public void testCondition() {
//1.获取entityManager对象
EntityManager em = JpaUtils.getEntityManager();
//2.开启事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//3.查询全部
//i.根据jpql语句创建Query查询对象
String jpql = "from Customer where custName like ? ";
Query query = em.createQuery(jpql);
//ii.对参数赋值 -- 占位符参数
//第一个参数:占位符的索引位置(从1开始),第二个参数:取值
query.setParameter(1,"传智播客%");

//iii.发送查询,并封装结果

/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
List list = query.getResultList();

for(Object obj : list) {
System.out.println(obj);
}

//4.提交事务
tx.commit();
//5.释放资源
em.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
//查询所有客户
@Test
public void findAll() {
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.getEntityManager();
//获取事务对象
tx = em.getTransaction();
tx.begin();
// 创建query对象
String jpql = "from Customer";
Query query = em.createQuery(jpql);
// 查询并得到返回结果
List list = query.getResultList(); // 得到集合返回类型
for (Object object : list) {
System.out.println(object);
}
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.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
30
31
32
33
34
//分页查询客户
@Test
public void findPaged () {
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.getEntityManager();
//获取事务对象
tx = em.getTransaction();
tx.begin();

//创建query对象
String jpql = "from Customer";
Query query = em.createQuery(jpql);
//起始索引
query.setFirstResult(0);
//每页显示条数
query.setMaxResults(2);
//查询并得到返回结果
List list = query.getResultList(); //得到集合返回类型
for (Object object : list) {
System.out.println(object);
}
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.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
//条件查询
@Test
public void findCondition () {
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.getEntityManager();
//获取事务对象
tx = em.getTransaction();
tx.begin();
//创建query对象
String jpql = "from Customer where custName like ? ";
Query query = em.createQuery(jpql);
//对占位符赋值,从1开始
query.setParameter(1, "传智播客%");
//查询并得到返回结果
Object object = query.getSingleResult(); //得到唯一的结果集对象
System.out.println(object);
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.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
30
//根据客户id倒序查询所有客户
//查询所有客户
@Test
public void testOrder() {
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.getEntityManager();
//获取事务对象
tx = em.getTransaction();
tx.begin();
// 创建query对象
String jpql = "from Customer order by custId desc";
Query query = em.createQuery(jpql);
// 查询并得到返回结果
List list = query.getResultList(); // 得到集合返回类型
for (Object object : list) {
System.out.println(object);
}
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.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
//统计查询
@Test
public void findCount() {
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.getEntityManager();
//获取事务对象
tx = em.getTransaction();
tx.begin();
// 查询全部客户
// 1.创建query对象
String jpql = "select count(custId) from Customer";
Query query = em.createQuery(jpql);
// 2.查询并得到返回结果
Object count = query.getSingleResult(); // 得到集合返回类型
System.out.println(count);
tx.commit();
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
} finally {
// 释放资源
em.close();
}
}

Spring Data JPA详解

spring data jpa的官方介绍

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!

Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦

Spring Data JPA的特性

spring data jpa的特性

SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢? 使用了SpringDataJpa,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法。

Spring Data JPA 与 JPA和hibernate之间的关系

JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)

Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。

Spring Data JPA的入门案例

需求说明

Spring Data JPA完成客户的基本CRUD操作

搭建Spring Data JPA的开发环境

创建工程导入坐标

使用Spring Data JPA,需要整合Spring与Spring Data JPA,并且需要提供JPA的服务提供者hibernate,所以需要导入spring相关坐标,hibernate坐标,数据库驱动坐标等

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.itcase</groupId>
<artifactId>jpademo2</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<spring.version>5.2.5.RELEASE</spring.version>
<hibernate.version>5.0.7.Final</hibernate.version>
<slf4j.version>1.7.26</slf4j.version>
<log4j.version>1.2.17</log4j.version>
<c3p0.version>0.9.1.2</c3p0.version>
<mysql.version>5.1.6</mysql.version>
</properties>

<dependencies>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- spring beg -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>

<!--spring对orm框架的支持包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring end -->

<!-- hibernate beg -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!--hibernate对jpa实现的包-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.7.Final</version>
</dependency>
<!-- hibernate end -->

<!-- c3p0 beg -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<!-- c3p0 end -->

<!-- log end -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->


<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>

<!--spring data jpa的坐标-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- el beg 使用spring data jpa 必须引入 -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>

<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<!-- el end -->
</dependencies>
</project>

配置spring的配置文件

配置spring Data jpa的整合,在resources文件下配置applicationContext.xml文件

image-20200502201740078

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<!--配置数据源-->
<!--<context:property-placeholder location="classpath:db.properties">-->
<!--spring和spring data jpa的配置-->
<!-- 1.dataSource 配置数据库连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa?useUnicode=true&amp;characterEncoding=UTF-8" />
<property name="user" value="root" />
<property name="password" value="root" />
</bean>

<!-- 2.创建entityManagerFactory对象交给spring容器管理 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--数据连接池-->
<property name="dataSource" ref="dataSource" />
<!--配置要扫描的包(实体类所在的包),自动扫描-->
<property name="packagesToScan" value="cn.itcase.domain" />
<!--jpa的实现厂家-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
</property>
<!--JPA的供应商适配器-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--配置是否自动创建数据库表-->
<property name="generateDdl" value="false" />
<!--指定数据库类型,注意需要大写-->
<property name="database" value="MYSQL" />
<!--数据库方言:支持的特有语法-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
<!--是否显示sql语句-->
<property name="showSql" value="true" />
</bean>
</property>
<!--jpa的方言:高级特性-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
</bean>


<!-- 3.事务管理器-->
<!-- JPA事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<!-- 4.整合spring data jpa-->
<!--dao接口所在的包下-->
<!--配置事务管理器-->
<!--将底层交给entityManagerFactory-->
<jpa:repositories base-package="cn.itcase.dao"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"> </jpa:repositories>

<!-- 5.txAdvice-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 6.aop-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* cn.itcase.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>

<!--配置包扫描-->
<context:component-scan base-package="cn.itcase"></context:component-scan>
<!--组装其它 配置文件-->
</beans>

事务管理器PlatformTransactionManager接口下的实现类

image-20200502185401887

这里使用JpaTransactionManager

注:

声明式事务一般配置到service层,txAdvice和aop是对声明式事务的配置

编写实体类(Customer)

image-20200502203640803

使用jpa注解配置映射关系

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* 1.实体类和表的映射关系
* @Eitity
* @Table
* 2.类中属性和表中字段的映射关系
* @Id
* @GeneratedValue
* @Column
*/
@Entity
@Table(name="cst_customer")
public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="cust_id")
private Long custId;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_name")
private String custName;
@Column(name="cust_phone")
private String custPhone;
@Column(name="cust_source")
private String custSource;

public Long getCustId() {
return custId;
}

public void setCustId(Long custId) {
this.custId = custId;
}

public String getCustAddress() {
return custAddress;
}

public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}

public String getCustIndustry() {
return custIndustry;
}

public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}

public String getCustLevel() {
return custLevel;
}

public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}

public String getCustName() {
return custName;
}

public void setCustName(String custName) {
this.custName = custName;
}

public String getCustPhone() {
return custPhone;
}

public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}

public String getCustSource() {
return custSource;
}

public void setCustSource(String custSource) {
this.custSource = custSource;
}

@Override
public String toString() {
return "Customer{" +
"custId=" + custId +
", custAddress='" + custAddress + '\'' +
", custIndustry='" + custIndustry + '\'' +
", custLevel='" + custLevel + '\'' +
", custName='" + custName + '\'' +
", custPhone='" + custPhone + '\'' +
", custSource='" + custSource + '\'' +
'}';
}
}

编写符合Spring Data JPA规范的Dao层接口

Spring Data JPA是spring提供的一款对于数据访问层(Dao层)的框架,使用Spring Data JPA,只需要按照框架的规范提供dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。

在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:

1.创建一个Dao层接口,并继承两个接口JpaRepository和JpaSpecificationExecutor

2.提供相应的泛型

image-20200502203640803

1
2
3
4
5
6
7
8
9
/**
* 符合SpringDataJpa的dao层接口规范
* JpaRepository<操作的实体类类型,实体类中主键属性的类型>
* * 封装了基本CRUD操作
* JpaSpecificationExecutor<操作的实体类类型>
* * 封装了复杂查询(比如分页操作等)
*/
public interface CustomerDao extends JpaRepository<Customer,Long> ,JpaSpecificationExecutor<Customer> {
}

这样我们就定义好了一个符合Spring Data JPA规范的Dao层接口

完成基本CRUD操作

完成了Spring Data JPA的环境搭建,并且编写了符合Spring Data JPA 规范的Dao层接口之后,就可以使用定义好的Dao层接口进行客户的基本CRUD操作

CustomerDaoTest测试类

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
@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;

/**
* 根据id查询
*/
@Test
public void testFindOne() {
//这是jpa1.0版本的写法
//Customer customer = customerDao.findOne(2l);
//这是jpa2.0版本的写法
Optional<Customer> customer = customerDao.findById(2l);
System.out.println(customer);
}

/**
* save : 保存或者更新,根据传递的对象是否存在主键id来判断是保存还是更新
*
* 如果没有id主键属性:保存
* 如果存在id主键属性,根据id查询数据,更新数据
*/
@Test
public void testSave() {
Customer customer = new Customer();
customer.setCustName("黑马程序员");
customer.setCustLevel("vip");
customer.setCustIndustry("it教育");
customerDao.save(customer);
}

//先查询出来数据再去更新,会将没有设置的值设置为null或0
@Test
public void testUpdate() {
Customer customer = new Customer();
customer.setCustId(3l);
customer.setCustName("黑马程序员很厉害");
customerDao.save(customer);
}

@Test
public void testDelete () {
Customer customer = new Customer();
customer.setCustId(3l);
customerDao.delete(customer);
}

/**
* 查询所有
*/
@Test
public void testFindAll() {
List<Customer> list = customerDao.findAll();
for(Customer customer : list) {
System.out.println(customer);
}
}
}

总结:

findById(id):根据id查询

save(customer):保存或者更新(依据传递的实体类对象中,是否包含id属性)

delete(customer):删除数据

findAll():查询全部

Spring Data JPA的内部原理剖析

Spring Data JPA的常用接口分析

在客户的案例中,我们发现在自定义的CustomerDao中,并没有提供任何方法就可以使用其中的很多方法,那么这些方法究竟是怎么来的呢?答案很简单,对于我们自定义的Dao接口,由于继承了JpaRepository和JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。

1
2
3
@Indexed
public interface Repository<T, ID> {
}

Repository是一个空接口,即是一个标记接口,若定义的接口继承了Repository,则该接口会被IOC容器识别为一个Repository Bean,进而纳入到IOC容器中,可以在该接口中定义满足一定规范的方法

基础的Repository提供了最基本的数据访问功能,其几个子接口则扩展了一些功能,它们的继承关系如下

–Repository:仅仅是一个标识,表名任何继承它的均为仓库接口类

–CrudRepository:继承Repository,实现了一组CRUD相关的方法

–PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法

–JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法

–自定义的XxxxRepository:需要继承JpaRepository,这样的XxxxRepository接口就具备了通用的数据访问控制层的能力

–JpaSpecificationExecutor:不属于Respository体系,实现一组JPA Criteria查询相关的方法

在使用Spring Data JPA时,一般实现JpaRepository和JpaSpecificationExecutor接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?

Spring Data JPA的实现过程

以findOne方法为例进行分析

代理子类的实现过程

004

断点执行到方法上时,我们可以发现注入的customerDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象

代理对象中方法调用的分析

当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对customerDao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository

006

通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?

005

带着问题继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发

<jpa:repositories>中的base-package代表的含义就是对此包下的所有接口进行动态代理增强,生成simpleJpaRepository实现类,所以在创建动态接口的时候一定要放在这个包下

springDataJpa的运行过程和原理剖析

1.通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象SimpleJpaRepository

2.SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)

3.通过hibernate完成数据库操作(封装了jdbc)

借助接口中的定义好的方法完成查询

注意

saveAndFlush()方法就相当于JPA中的merge()方法

如果修改中包含id,则是修改更新操作,如果不包含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
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

@Override
List<T> findAll();

@Override
List<T> findAll(Sort sort);

@Override
List<T> findAllById(Iterable<ID> ids);

@Override
<S extends T> List<S> saveAll(Iterable<S> entities);

void flush();

<S extends T> S saveAndFlush(S entity);

void deleteInBatch(Iterable<T> entities);

void deleteAllInBatch();

T getOne(ID id);

@Override
<S extends T> List<S> findAll(Example<S> example);

@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
1
2
3
4
5
6
7
8
9
/**
* 测试统计查询:查询客户的总数量
* count:统计总条数
*/
@Test
public void testCount() {
long count = customerDao.count();//查询全部的客户数量
System.out.println(count);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 根据id从数据库查询
* 加上注解@Transactional : 来保证getOne正常运行
*
* findById:
* em.find(domainType, id) :立即加载
* getOne:
* em.getReference(this.getDomainClass(), id) :延迟加载
* * 返回的是一个客户的动态代理对象
* * 什么时候用,什么时候查询
*/
@Test
@Transactional
public void testGetOne() {
Customer customer = customerDao.getOne(4l);
System.out.println(customer);
}

@Query注解

使用JPQL的方式查询

注意:JPQL不支持inset语句

使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询

jpql : jpa query language (jpq查询语言),特点是语法或关键字和sql语句类似,而且查询的是类和类中的属性

需要将JPQL语句配置到接口方法上

  1. 特有的查询:需要在dao接口上配置方法
  2. 在新添加的方法上,使用注解的形式配置jpql查询语句
  3. 注解 : @Query

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

根据客户名称查询客户

在CustomerDao中添加如下代码

1
2
3
4
5
6
7
8
9
/**
* 案例:根据客户名称查询客户
* 使用jpql的形式查询
* jpql:from Customer where custName = ?
*
* 配置jpql语句,使用的@Query注解
*/
@Query(value="from Customer where custName = ?1")
public Customer findJpql(String custName);

在JpqlTest测试类中添加如下代码

1
2
3
4
5
@Test
public void testFindJPQL() {
Customer customer = customerDao.findJpql("传智播客");
System.out.println(customer);
}

根据客户名称和客户id查询客户

在CustomerDao中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 案例:根据客户名称和客户id查询客户
* jpql: from Customer where custName = ? and custId = ?
*
* 对于多个占位符参数
* 赋值的时候,默认的情况下,占位符的位置需要和方法参数中的位置保持一致
*
* 可以指定占位符参数的位置
* ? 索引的方式,指定此占位的取值来源
*/
@Query(value = "from Customer where custName = ?2 and custId = ?1")
public Customer findCustNameAndId(Long id,String name);

在JpqlTest测试类中添加如下代码

1
2
3
4
5
6
@Test
public void testFindCustNameAndId() {
// Customer customer = customerDao.findCustNameAndId("传智播客",1l);
Customer customer = customerDao.findCustNameAndId(1l,"传智播客");
System.out.println(customer);
}

测试jpql的更新操作

在CustomerDao中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 使用jpql完成更新操作
* 案例 : 根据id更新,客户的名称
* 更新4号客户的名称,将名称改为“黑马程序员”
*
* sql :update cst_customer set cust_name = ? where cust_id = ?
* jpql : update Customer set custName = ? where custId = ?
*
* @Query : 代表的是进行查询
* * 声明此方法是用来进行更新操作
* @Modifying
* * 当前执行的是一个更新操作
*
*/
@Query(value = " update Customer set custName = ?2 where custId = ?1 ")
@Modifying
public void updateCustomer(long custId,String custName);

在JpqlTest测试类中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 测试jpql的更新操作
* * springDataJpa中使用jpql完成 更新/删除操作
* * 需要手动添加事务的支持
* * 默认会执行结束之后,回滚事务
* @Rollback : 设置是否自动回滚
* false | true
*/
@Test
@Transactional //添加事务的支持
@Rollback(value = false)
public void testUpdateCustomer() {
customerDao.updateCustomer(4l,"黑马程序员");
}

注:

CustomerRepository接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface CustomerRepository extends Repository<Customer,Long> {

//使用@Query注解可以自定义jpql语句以实现更灵活的查询
//查询id值最大的Person
@Query("select p from Customer p where p.custId=(select max(p2.custId) from Customer p2)")
Customer getMaxIdPerson();

//为@Query注解传递参数的方式1:使用占位符
@Query("select p from Customer p where p.custName=?1 and p.custPhone=?2")
List<Customer> testQueryAnnotationParams(String custname,String phone);

//为@Query注解传递参数的方式2:命名参数的形式
@Query("select p from Customer p where p.custName= :custName and p.custPhone= :custPhone ")
List<Customer> testQueryAnnotationParams2(@Param("custName") String custname, @Param("custPhone") String phone);

//SpringData允许在占位符上添加%%
@Query("select p from Customer p where p.custName like %?1% and p.custPhone like %?2%")
List<Customer> testQueryAnnotationLikeParams( String custname, String phone);

//为@Query注解传递参数的方式2:命名参数的形式
@Query("select p from Customer p where p.custName like %:custName% and p.custPhone like %:custPhone% ")
List<Customer> testQueryAnnotationLikeParams2(@Param("custName") String custname, @Param("custPhone") String phone);
}

测试代码

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
@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;

@Autowired
private CustomerRepository customerRepository;

@Test
public void testQueryAnnotation(){
Customer maxIdPerson = customerRepository.getMaxIdPerson();
System.out.println(maxIdPerson);
}

@Test
public void testQueryAnnontationParam(){
//List<Customer> customers = customerRepository.testQueryAnnotationParams("AA", "SS");
List<Customer> customers = customerRepository.testQueryAnnotationParams2("AA", "SS");
System.out.println(customers);
}

@Test
public void testQueryAnnontationLike(){
//List<Customer> customers = customerRepository.testQueryAnnotationParams("AA", "SS");
List<Customer> customers = customerRepository.testQueryAnnotationLikeParams("%A", "S%");
System.out.println(customers);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 可以通过自定义的JPQL完成UPDATE和DELETE操作,注意jpql不支持使用INSERT
* 在@Query注解中编写JPQL语句,但必须使用@Modifying注解进行修饰,以通知SpringData这是一个UPDATE或DELETE操作
* UPDATE或DELETE操作需要使用事务,此时需要定义Service层,在Service层的方法上添加事务操作(这里为了方便在 Dao层上添加)
* 默认情况下,SpringData的每个方法上有事务,但都是一个只读事务,它们不能完成修改操作
* @param id
* @param custName
*/
@Modifying
@Transactional
@Query("UPDATE Customer p set p.custName=:custName where p.custId=:custId")
void updateCustomerEmail(@Param("custId") Long id,@Param("custName") String custName);

测试类

1
2
3
4
5
6
@Test
public void testQueryAnnontationUpdate(){

customerRepository.updateCustomerEmail(302l, "zhangxiaoyu");

}

sql语句的查询

  1. 特有的查询:需要在dao接口上配置方法

  2. 在新添加的方法上,使用注解的形式配置sql查询语句

  3. 注解 : @Query
    value :jpql语句 | sql语句

    nativeQuery :是否使用本地查询,false(使用jpql查询) | true(使用本地查询:sql查询),默认是false

查询全部的客户

在CustomerDao中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 使用sql的形式查询:
* 查询全部的客户
* sql : select * from cst_customer;
* Query : 配置sql查询
* value : sql语句
* nativeQuery : 查询方式
* true : sql查询
* false:jpql查询
*
*/
//@Query(value = " select * from cst_customer" ,nativeQuery = true)
@Query(value="select * from cst_customer where cust_name like ?1",nativeQuery = true)
public List<Object [] > findSql(String name);

在JpqlTest测试类中添加如下代码

1
2
3
4
5
6
7
8
9
//测试sql查询
@Test
public void testFindSql() {
//List<Object[]> list = customerDao.findSql();
List<Object[]> list = customerDao.findSql("传智播客%");
for(Object [] obj : list) {
System.out.println(Arrays.toString(obj));
}
}

方法命名规则查询

顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析

在Repository子接口中声明方法,不是随便声明的,需要符合一定的规范,查询方法以find|read|get开头,涉及条件查询的时候,条件的属性用条件关键字连接,要注意条件的属性以首字母大写

支持属性的级联查询,若当前有符合条件的属性,则优先使用,而不使用级联属性,若要是要级联属性,则属性之间使用_进行连接

在CustomerDao中添加如下代码

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
/**
* 方法名的约定:
* findBy : 代表查询
* 对象中的属性名(首字母大写) : 查询的条件,默认情况使用等于的方式查询
*
*
*
* findByCustName -- 根据客户名称查询,custName首字母改为大写CustName
*
* 在springdataJpa的运行阶段,会根据方法名称进行解析,会把findBy翻译成from xxx(实体类),并将
* 属性名称翻译为where custName =
*
* 1.findBy + 属性名称 (根据属性名称进行完成匹配的查询=)
*
* 2.findBy + 属性名称 + “查询方式(Like | isnull)” 如findByCustNameLike
*
* 3.多条件查询
* findBy + 属性名 + “查询方式” + “多条件的连接符(and|or)” + 属性名 + “查询方式”
*/

public Customer findByCustName(String custName);


public List<Customer> findByCustNameLike(String custName);

//使用客户名称模糊匹配和客户所属行业精准匹配的查询
//此时参数的顺序不能弄反
public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);

在JpqlTest测试类中添加如下代码

1
2
3
4
5
6
//测试方法命名规则的查询
@Test
public void testNaming() {
Customer customer = customerDao.findByCustName("传智播客");
System.out.println(customer);
}
1
2
3
4
5
6
7
8
//测试方法命名规则的查询
@Test
public void testFindByCustNameLike() {
List<Customer> list = customerDao.findByCustNameLike("传智播客%");
for (Customer customer : list) {
System.out.println(customer);
}
}
1
2
3
4
5
6
//测试方法命名规则的查询
@Test
public void testFindByCustNameLikeAndCustIndustry() {
Customer customer = customerDao.findByCustNameLikeAndCustIndustry("传智播客1%", "it教育");
System.out.println(customer);
}

具体的关键字,使用方法和生产成SQL如下表所示

Keyword Sample JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs, findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* JpaSpecificationExecutor中定义的方法
**/
public interface JpaSpecificationExecutor<T> {
//根据条件查询一个对象
T findOne(Specification<T> spec);
//根据条件查询集合
List<T> findAll(Specification<T> spec);
//根据条件分页查询
//pageable:分页参数
//返回值:分页pageBean(page:是springdatajpa提供的)
Page<T> findAll(Specification<T> spec, Pageable pageable);
//排序查询查询
//Sort:排序参数
List<T> findAll(Specification<T> spec, Sort sort);
//统计查询
long count(Specification<T> spec);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

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
public interface Specification<T> extends Serializable {
long serialVersionUID = 1L;

static <T> Specification<T> not(@Nullable Specification<T> spec) {
return spec == null ? (root, query, builder) -> {
return null;
} : (root, query, builder) -> {
return builder.not(spec.toPredicate(root, query, builder));
};
}

@Nullable
static <T> Specification<T> where(@Nullable Specification<T> spec) {
return spec == null ? (root, query, builder) -> {
return null;
} : spec;
}

@Nullable
default Specification<T> and(@Nullable Specification<T> other) {
return SpecificationComposition.composed(this, other, (builder, left, rhs) -> {
return builder.and(left, rhs);
});
}

@Nullable
default Specification<T> or(@Nullable Specification<T> other) {
return SpecificationComposition.composed(this, other, (builder, left, rhs) -> {
return builder.or(left, rhs);
});
}

@Nullable
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}
1
2
3
4
//root:查询的根对象(实体类)(查询的任何属性都可以从根对象中获取)
//CriteriaQuery:顶层查询对象,自定义查询方式(了解:一般不用)
//CriteriaBuilder:查询的构造器,封装了很多的查询条件,用于创建Criteria相关对象的工厂,当然可以从中获取到Predicate对象
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); //封装查询条件

根据条件,查询单个对象

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
/**
* 根据条件,查询单个对象
*
*/
@Test
public void testSpec() {
//匿名内部类
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询的对象类型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借助方法参数中的两个参数(
* root:获取需要查询的对象属性
* CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
* )
* 案例:根据客户名称查询,查询客户名为传智播客的客户
* 查询条件:1.查询方式 查询方式全部存在于CriteriaBuilder对象中 2.比较的属性名称 比较属性全部存在于root对象中
*/
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1.获取比较的属性
Path<Object> custName = root.get("custName");
//2.构造查询条件 : select * from cst_customer where cust_name = '传智播客'
/**
* 第一个参数:需要比较的属性(path对象)
* 第二个参数:当前需要比较的取值
*/
Predicate predicate = cb.equal(custName, "传智播客");//进行精准的匹配 (比较的属性,比较的属性的取值)
return predicate;
}
};
Optional<Customer> customer = customerDao.findOne(spec);
System.out.println(customer);
}

多条件查询

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
/**
* 多条件查询
* 案例:根据客户名(传智播客)和客户所属行业查询(it教育)
*
*/
@Test
public void testSpec1() {
/**
* root:获取属性
* 客户名
* 所属行业
* cb:构造查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3.将以上两个查询联系起来
*/
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Object> custName = root.get("custName");//客户名
Path<Object> custIndustry = root.get("custIndustry");//所属行业

//构造查询
//1.构造客户名的精准匹配查询
Predicate p1 = cb.equal(custName, "传智播客");//第一个参数,path(属性),第二个参数,属性的取值
//2..构造所属行业的精准匹配查询
Predicate p2 = cb.equal(custIndustry, "it教育");
//3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系)
Predicate and = cb.and(p1, p2);//以与的形式拼接多个查询条件
// cb.or();//以或的形式拼接多个查询条件
return and;
}
};
Optional<Customer> customer = customerDao.findOne(spec);
System.out.println(customer);
}

完成根据客户名称的模糊匹配,返回客户列表

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
/**
* 案例:完成根据客户名称的模糊匹配,返回客户列表
* 客户名称以 ’传智播客‘ 开头
*
* equal :直接的到path对象(属性),然后进行比较即可
* gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较
* 指定参数类型的方式是:path.as(类型的字节码对象)
*/
@Test
public void testSpec3() {
//构造查询条件
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//查询属性:客户名
Path<Object> custName = root.get("custName");
//查询方式:模糊匹配
Predicate like = cb.like(custName.as(String.class), "传智播客%");
return like;
}
};
List<Customer> list = customerDao.findAll(spec);
for (Customer customer : list) {
System.out.println(customer);
}
}

对上面的结果添加排序

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
@Test
public void testSpec3() {
//构造查询条件
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//查询属性:客户名
Path<Object> custName = root.get("custName");
//查询方式:模糊匹配
Predicate like = cb.like(custName.as(String.class), "传智播客%");
return like;
}
};
//添加排序
//使用Sort.by()进行排序
//第一个参数:排序的顺序(倒序,正序)
// Sort.Direction.DESC:倒序
// Sort.Direction.ASC : 升序
//第二个参数:排序的属性名称
Sort sort = Sort.by(Sort.Direction.DESC, "custId");
List<Customer> list = customerDao.findAll(spec, sort);
for (Customer customer : list) {
System.out.println(customer);
}
}

分页查询

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
/**
* 分页查询
* Specification: 查询条件
* Pageable:分页参数:表示查询的页码,每页查询的条数
* findAll(Specification,Pageable):带有条件的分页
* findAll(Pageable):没有条件的分页
* 返回:Page(springDataJpa为我们封装好的pageBean对象,数据列表,共条数)
* Page<T> findAll(Specification<T> spec, Pageable pageable);
* Specification:封装了JPA Criteria查询的查询条件
*/
@Test
public void testSpec4() {

//PageRequest对象是Pageable接口的实现类
/*
* 创建PageRequest的过程中,需要调用他的构造方法传入两个参数
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
*/
PageRequest pageable =PageRequest.of(0, 2);
//分页查询
Page<Customer> page = customerDao.findAll(pageable);
System.out.println(page.getContent()); //得到数据集合列表
System.out.println(page.getTotalElements());//得到总条数
System.out.println(page.getTotalPages());//得到总页数
}

方法对应关系

方法名称 Sql对应关系
equal filed = value
gt(greaterThan ) filed > value
lt(lessThan ) filed < value
ge(greaterThanOrEqualTo ) filed >= value
le( lessThanOrEqualTo) filed <= value
notEqual filed != value
like filed like value
notLike filed not like value

多表设计

在applicationContext.xml中添加如下配置

在进行一对多或者多对一的时候一定要添加这个配置,否则可能报错

image-20200503182151616

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
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--数据连接池-->
<property name="dataSource" ref="dataSource" />
<!--配置要扫描的包(实体类所在的包),自动扫描-->
<property name="packagesToScan" value="cn.itcase.domain" />
<!--jpa的实现厂家-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
</property>
<!--JPA的供应商适配器-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--配置是否自动创建数据库表-->
<property name="generateDdl" value="false" />
<!--指定数据库类型,注意需要大写-->
<property name="database" value="MYSQL" />
<!--数据库方言:支持的特有语法-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
<!--是否显示sql语句-->
<property name="showSql" value="true" />
</bean>
</property>
<!--jpa的方言:高级特性-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>

表之间关系的划分

数据库中多表之间存在着三种关系,如图所示。

001

从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。

明确: 我们今天只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。

1
2
3
4
5
6
7
8
表关系
一对一
一对多:
一的一方:称为主表
多的一方:称为从表
外键:需要再从表上新建一列作为外键,他的取值来源于主表的主键
多对多:
中间表:中间表中最少应该由两个字段组成,这两个字段做为外键指向两张表的主键,又组成了联合主键

在JPA框架中表关系的分析步骤

在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数

  1. 明确表关系
  2. 确定表关系(描述 外键|中间表)
  3. 编写实体类,再实体类中描述表关系
  4. 配置映射关系

映射的注解说明

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
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除

@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。

JPA中的一对多

案例

客户和联系人的案例(一对多关系)

客户:一家公司

联系人:这家公司的员工

一个客户可以具有多个联系人,一个联系人从属于一家公司

002

分析步骤

  1. 明确表关系

    一对多关系

  2. 确定表关系(描述 外键|中间表)

    主表:客户表
    从表:联系人表,在从表上添加外键,它的取值是主表的主键

  3. 编写实体类,在实体类中描述表关系(包含关系)

    客户:在客户的实体类中包含一个联系人的集合

    联系人:在联系人的实体类中包含一个客户的对象

    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
    /*创建客户表*/
    CREATE TABLE cst_customer (
    cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
    cust_name varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
    cust_source varchar(32) DEFAULT NULL COMMENT '客户信息来源',
    cust_industry varchar(32) DEFAULT NULL COMMENT '客户所属行业',
    cust_level varchar(32) DEFAULT NULL COMMENT '客户级别',
    cust_address varchar(128) DEFAULT NULL COMMENT '客户联系地址',
    cust_phone varchar(64) DEFAULT NULL COMMENT '客户联系电话',
    PRIMARY KEY (`cust_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;

    /*创建联系人表*/
    CREATE TABLE cst_linkman (
    lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
    lkm_name varchar(16) DEFAULT NULL COMMENT '联系人姓名',
    lkm_gender char(1) DEFAULT NULL COMMENT '联系人性别',
    lkm_phone varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
    lkm_mobile varchar(16) DEFAULT NULL COMMENT '联系人手机',
    lkm_email varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
    lkm_position varchar(16) DEFAULT NULL COMMENT '联系人职位',
    lkm_memo varchar(512) DEFAULT NULL COMMENT '联系人备注',
    lkm_cust_id bigint(32) NOT NULL COMMENT '客户id(外键)',
    PRIMARY KEY (`lkm_id`),
    KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
    CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  4. 配置映射关系

    使用jpa注解配置一对多映射关系

一对多关系

在Customer实体类中配置客户和联系人之间的关系(一对多关系)

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
//配置客户和联系人之间的关系(一对多关系)
/**
* 使用注解的形式配置多表关系
* 1.声明表关系
* @OneToMany : 配置一对多关系
* targetEntity :对方对象的字节码对象
* 2.配置外键(中间表)
* @JoinColumn : 配置外键
* name:外键字段名称
* referencedColumnName:参照的主表的主键字段名称
*
* 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
*
*/

@OneToMany(targetEntity = LinkMan.class)
//外键的名字是参照数据库对应表中的外键字段的名字
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Set<LinkMan> linkMans = new HashSet<>();

public Set<LinkMan> getLinkMans() {
return linkMans;
}

public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}

多对一关系

在LinkMan实体类中配置联系人和客户之间的关系(多对一关系)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 配置联系人到客户的多对一关系
* 使用注解的形式配置多对一关系
* 1.配置表关系
* @ManyToOne : 配置多对一关系
* targetEntity:对方的实体类字节码
* 2.配置外键(中间表)
*
* * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
*
*/
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
//外键的名字是参照数据库对应表中的外键字段的名字
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;

public Customer getCustomer() {
return customer;
}

public void setCustomer(Customer customer) {
this.customer = customer;
}

注意双向关系,单项一对多,单项多对一之间的关系

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 保存一个客户,保存一个联系人
*
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚,默认是自动回滚
public void testAdd() {
//创建一个客户,创建一个联系人
Customer customer = new Customer();
customer.setCustName("百度");

LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小李");

/**
* 配置了客户到联系人的关系
* 从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
* 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
*/
customer.getLinkMans().add(linkMan);
customerDao.save(customer);
linkManDao.save(linkMan);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 会有一条多余的update语句
* * 由于一的一方可以维护外键:会发送update语句
* * 解决此问题:只需要在一的一方放弃维护权即可
*
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd2() {
//创建一个客户,创建一个联系人
Customer customer = new Customer();
customer.setCustName("百度");

LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小李");


linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)
customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句)

customerDao.save(customer);
linkManDao.save(linkMan);
}

放弃外键维护权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 放弃外键维护权
* mappedBy:参照对方配置关系的属性名称
* cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
* CascadeType.all : 所有
* MERGE :更新
* PERSIST :保存
* REMOVE :删除
* <p>
* fetch : 配置关联对象的加载方式
* EAGER :立即加载
* LAZY :延迟加载
*/
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private Set<LinkMan> linkMans = new HashSet<>();

级联

操作一个对象的同时操作他的关联对象

级联操作

​ 1. 需要区分操作主体,通过谁来进行操作

​ 2. 需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)

​ 3. cascade(配置级联)

级联添加

​ 案例:当我保存一个客户的同时保存联系人

级联删除

​ 案例:当我删除一个客户的同时删除此客户的所有联系人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 放弃外键维护权
* mappedBy:对方配置关系的属性名称\
* cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
* CascadeType.all : 所有
* MERGE :更新
* PERSIST :保存
* REMOVE :删除
* <p>
* fetch : 配置关联对象的加载方式
* EAGER :立即加载
* LAZY :延迟加载
*/
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private Set<LinkMan> linkMans = new HashSet<>();

级联添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 级联添加:保存一个客户的同时,保存客户的所有联系人
* 需要在操作主体的实体类上,配置casacde属性
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeAdd() {
Customer customer = new Customer();
customer.setCustName("百度1");

LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小李1");

linkMan.setCustomer(customer);
customer.getLinkMans().add(linkMan);

customerDao.save(customer);
}

cst_customer表

image-20200504083652067

cst_linkman表

image-20200504083721932

级联删除

在applicationContext.xml中将hibernate.hbm2ddl.auto中的属性create改为update

1
2
3
4
5
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 级联删除:
* 删除1号客户的同时,删除1号客户的所有联系人
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeRemove() {
//1.查询1号客户
Optional<Customer> customer = customerDao.findById(1l);

//2.删除1号客户
customerDao.delete(customer.get());
}

JPA中的多对多

多对多操作

案例:用户和角色(多对多关系)

采用的示例为用户和角色。

用户:指的是咱们班的每一个同学。

角色:指的是咱们班同学的身份信息。

比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。

同时B同学,它也具有学生和子女的身份。

那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。

所以我们说,用户和角色之间的关系是多对多。

分析步骤

  1. 明确表关系

    多对多关系

  2. 确定表关系(描述 外键|中间表)

    中间间表

  3. 编写实体类,再实体类中描述表关系(包含关系)
    用户:包含角色的集合
    角色:包含用户的集合

  4. 配置映射关系

表关系建立

多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:

003

Role表

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
@Entity
@Table(name = "sys_role")
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;


public Long getRoleId() {
return roleId;
}

public void setRoleId(Long roleId) {
this.roleId = roleId;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}
}

User表

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
@Entity
@Table(name = "sys_user")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="age")
private Integer age;

public Long getUserId() {
return userId;
}

public void setUserId(Long userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

User表映射配置

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
67
68
69
@Entity
@Table(name = "sys_user")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="age")
private Integer age;

/**
* 配置用户到角色的多对多关系
* 配置多对多的映射关系
* 1.声明表关系的配置
* @ManyToMany(targetEntity = Role.class) //多对多
* targetEntity:代表对方的实体类字节码
* 2.配置中间表(中间表应包含两个外键)
* @JoinTable
* name : 中间表的名称
* joinColumns:配置当前对象在中间表的外键
* @JoinColumn的数组
* name:外键名
* referencedColumnName:参照的主表的主键名
* inverseJoinColumns:配置对方对象在中间表的外键
*/
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表中的外键
joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
//inverseJoinColumns,对方对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();

public Long getUserId() {
return userId;
}

public void setUserId(Long userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Set<Role> getRoles() {
return roles;
}

public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}

role表映射配置

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
@Entity
@Table(name = "sys_role")
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;

//配置多对多
@ManyToMany(targetEntity = User.class)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表中的外键
joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
//inverseJoinColumns,对方对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}
)
private Set<User> users = new HashSet<>();

public Long getRoleId() {
return roleId;
}

public void setRoleId(Long roleId) {
this.roleId = roleId;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}

public Set<User> getUsers() {
return users;
}

public void setUsers(Set<User> users) {
this.users = users;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 保存一个用户,保存一个角色
*/
@Test
@Transactional
@Rollback(false)
public void testAdd() {
User user = new User();
user.setUserName("小李");

Role role = new Role();
role.setRoleName("java程序员");

//配置用户到角色关系,可以对中间表中的数据进行维护 1-1
user.getRoles().add(role);

//配置角色到用户的关系,可以对中间表的数据进行维护 1-1
role.getUsers().add(user);

userDao.save(user);
roleDao.save(role);
}

此时会报错

1
2
3
4
5
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1-1' for key 'PRIMARY'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)

这是因为主键已经重复,此时解决的办法就是有一方放弃主键维护权

更改如下

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
@Entity
@Table(name = "sys_role")
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;

//配置多对多
@ManyToMany(mappedBy = "roles") //配置多表关系
private Set<User> users = new HashSet<>();

public Long getRoleId() {
return roleId;
}

public void setRoleId(Long roleId) {
this.roleId = roleId;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}

public Set<User> getUsers() {
return users;
}

public void setUsers(Set<User> users) {
this.users = users;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 保存一个用户,保存一个角色
*
* 多对多放弃维护权:被动的一方放弃
*/
@Test
@Transactional
@Rollback(false)
public void testAdd() {
User user = new User();
user.setUserName("小李");

Role role = new Role();
role.setRoleName("java程序员");

//配置用户到角色关系,可以对中间表中的数据进行维护 1-1
user.getRoles().add(role);

//配置角色到用户的关系,可以对中间表的数据进行维护 1-1
role.getUsers().add(user);

userDao.save(user);
roleDao.save(role);
}

测试结果

image-20200504102230302

image-20200504102256455

image-20200504102319218

级联操作

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
67
68
69
@Entity
@Table(name = "sys_user")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="age")
private Integer age;

/**
* 配置用户到角色的多对多关系
* 配置多对多的映射关系
* 1.声明表关系的配置
* @ManyToMany(targetEntity = Role.class) //多对多
* targetEntity:代表对方的实体类字节码
* 2.配置中间表(包含两个外键)
* @JoinTable
* name : 中间表的名称
* joinColumns:配置当前对象在中间表的外键
* @JoinColumn的数组
* name:外键名
* referencedColumnName:参照的主表的主键名
* inverseJoinColumns:配置对方对象在中间表的外键
*/
@ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表中的外键
joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
//inverseJoinColumns,对方对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();

public Long getUserId() {
return userId;
}

public void setUserId(Long userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Set<Role> getRoles() {
return roles;
}

public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}

此时就会删除成功

多表的查询

对象导航查询,查询一个对象的同时,通过此对象查询他的关联对象

使用案例推荐一对多,即客户和联系人,因为它既能包含对象属性,又能包含集合

从一方查询多方

默认:使用延迟加载

对于一方查询多方,一定要使用延迟加载

从多方查询一方

默认:使用立即加载,推荐立即加载

从一方查询多方

测试类1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired
private CustomerDao customerDao;

@Autowired
private LinkManDao linkManDao;

//出现错误could not initialize proxy - no Session
//测试对象导航查询(查询一个对象的时候,通过此对象查询所有的关联对象)
@Test
@Transactional // 解决在java代码中的no session问题
public void testQuery1() {
//查询id为1的客户
Customer customer = customerDao.getOne(1l);
//对象导航查询,此客户下的所有联系人
Set<LinkMan> linkMans = customer.getLinkMans();

for (LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
}

延迟加载与立即加载

测试类2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 对象导航查询:
* 默认使用的是延迟加载的形式查询的
* 调用get方法并不会立即发送查询,而是在使用关联对象的时候才会查询,即延迟加载!
* 如果不想使用延迟查询,只需要修改配置,将延迟加载改为立即加载
* 需要使用fetch,并将其配置到多表映射关系的注解上
*
*/

@Test
@Transactional // 解决在java代码中的no session问题
public void testQuery2() {
//查询id为1的客户
Optional<Customer> customer = customerDao.findById(1l);
//对象导航查询,此客户下的所有联系人
Set<LinkMan> linkMans = customer.get().getLinkMans();

System.out.println(linkMans.size());
}

Customer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 放弃外键维护权
* mappedBy:对方配置关系的属性名称\
* cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
* CascadeType.all : 所有
* MERGE :更新
* PERSIST :保存
* REMOVE :删除
* <p>
* fetch : 配置关联对象的加载方式
* EAGER :立即加载
* LAZY :延迟加载
*/
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>();

立即加载会以左外连接的方式查询出所有的数据,对于立即加载不推荐使用

从多方查询一方

从多方查询一方

默认:使用立即加载

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 从联系人对象导航查询他的所属客户
* * 默认 : 立即加载
* 若想改为延迟加载:则在多的一方(联系人)处添加fetch = FetchType.LAZY
*
*/
@Test
@Transactional // 解决在java代码中的no session问题
public void testQuery3() {
Optional<LinkMan> linkMan = linkManDao.findById(2l);
//对象导航查询所属的客户
Customer customer = linkMan.get().getCustomer();
System.out.println(customer);
}

LinkMan类

1
2
3
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;

使用Example快速实现动态查询

Example官方介绍

按例查询(QBE是Query By Example(通过例子进行查询)的简称)是一种用户界面友好的查询技术。 它允许动态创建查询,并且不需要编写包含字段名称的查询。 实际上,按示例查询不需要使用特定的数据库的查询语言来编写查询语句。

优点:

  • 可以使用动态或者静态的限制去查询
  • 在重构你的实体的时候,不用担心影响已有的查询
  • 可以独立地工作在数据查询API之外

缺点

  • 属性不支持嵌套或者分组约束,比如这样的查询 firstname = ?0 or (firstname = ?1 and lastname = ?2)
  • 灵活匹配只支持字符串类型,其他类型只支持精确匹配

Example api的组成

  1. Probe: 含有对应字段的实例对象。
  2. ExampleMatcher:ExampleMatcher携带有关如何匹配特定字段的详细信息,相当于匹配条件。
  3. Example:由Probe和ExampleMatcher组成,用于查询。

案例

创建实体映射

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
@Entity
@Table(name="t_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Column(name="username")
private String username;

@Column(name="password")
private String password;

@Column(name="email")
private String email;

@Column(name="phone")
private String phone;

@Column(name="address")
private String address;
}

测试查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void contextLoads() {
User user = new User();
user.setUsername("admin");
Example<User> example = Example.of(user);
List<User> list = userRepository.findAll(example);
System.out.println(list);
}

打印的sql语句如下:

Hibernate:
select
user0_.id as id1_0_,
user0_.address as address2_0_,
user0_.email as email3_0_,
user0_.password as password4_0_,
user0_.phone as phone5_0_,
user0_.username as username6_0_
from
t_user user0_
where
user0_.username=?

可以发现,试用Example查询,默认情况下会忽略空值,官方文档也有说明:

This is a simple domain object. You can use it to create an Example. By default, fields having null values are ignored, and strings are matched using the store specific defaults. Examples can be built by either using the of factory method or by using ExampleMatcher. Example is immutable.

在上面的测试之中,我们只是只是定义了Probe而没有ExampleMatcher,是因为默认会不传时会使用默认的匹配器。点进方法可以看到下面的代码

1
2
3
4
5
6
7
8
9
10
11
static <T> Example<T> of(T probe) {
return new TypedExample(probe, ExampleMatcher.matching());
}

static ExampleMatcher matching() {
return matchingAll();
}

static ExampleMatcher matchingAll() {
return (new TypedExampleMatcher()).withMode(ExampleMatcher.MatchMode.ALL);
}

自定匹配器规则

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
@Test
public void contextLoads() {
User user = new User();
user.setUsername("y");
user.setAddress("sh");
user.setPassword("admin");
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("username", ExampleMatcher.GenericPropertyMatchers.startsWith())//模糊查询匹配开头,即{username}%
.withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains())//全部模糊查询,即%{address}%
.withIgnorePaths("password");//忽略字段,即不管password是什么值都不加入查询条件
Example<User> example = Example.of(user ,matcher);
List<User> list = userRepository.findAll(example);
System.out.println(list);
}

打印的sql语句如下:
select
user0_.id as id1_0_,
user0_.address as address2_0_,
user0_.email as email3_0_,
user0_.password as password4_0_,
user0_.phone as phone5_0_,
user0_.username as username6_0_
from
t_user user0_
where
(
user0_.username like ?
)
and (
user0_.address like ?
)

参数如下:
2018-03-24 13:26:57.425 TRACE 5880 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [y%]
2018-03-24 13:26:57.425 TRACE 5880 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [%sh%]

补充

官方创建ExampleMatcher例子(1.8 lambda)

1
2
3
4
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}

StringMatcher 参数

image-20200504153143677

说明:

  1. 在默认情况下(没有调用withIgnoreCase())都是大小写敏感的
  2. regex是关于正则的,但没有什么用

总结

  1. 通过在使用springdata jpa时可以通过Example来快速的实现动态查询,同时配合Pageable可以实现快速的分页查询功能。
  2. 对于非字符串属性的只能精确匹配,比如想查询在某个时间段内注册的用户信息,就不能通过Example来查询

自定义Repository方法

为某一个Repository上添加自定义方法

步骤:

–定义一个接口:声明要添加,并声明要添加的方法

–提供非接口的实现类:类名需在要声明的Repository后添加Impl,并实现方法

–声明Repository接口,并继承第一步声明的接口

–进行使用

image-20200506131745280

注意:默认情况下,SpringData会在basepackage中查找“接口名Impl”作为实现类,也可以通过repository-impl-postfix声明后缀

CustomerDao接口

1
2
3
public interface CustomerDao {
void test();
}

CustomerRepositoryImpl类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CustomerRepositoryImpl implements CustomerDao {


@PersistenceContext
private EntityManager entityManager;

@Override
public void test() {
Customer customer = entityManager.find(Customer.class, 302L);
System.out.println("-->" + customer);

}

}

spring注解@PersistenceContext详细使用说明 :

这个是JPA中的注解,PersistenceContext,称为持久化上下文,它一般包含有当前事务范围内的,被管理的实体对象(Entity)的数据。每个EntityManager,都会跟一个PersistenceContext相关联。PersistenceContext中存储的是实体对象的数据,而关系数据库中存储的是记录。

CustomerRepository接口

1
2
3
public interface CustomerRepository extends Repository<Customer,Long> ,CustomerDao{

}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {


@Autowired
private CustomerRepository customerRepository;

@Test
public void testCustomRepositoryMethod(){
customerRepository.test();
}
}

输出结果

1
2
Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_, linkmans1_.lkm_cust_id as lkm_cust9_1_1_, linkmans1_.lkm_id as lkm_id1_1_1_, linkmans1_.lkm_id as lkm_id1_1_2_, linkmans1_.lkm_cust_id as lkm_cust9_1_2_, linkmans1_.lkm_email as lkm_emai2_1_2_, linkmans1_.lkm_gender as lkm_gend3_1_2_, linkmans1_.lkm_memo as lkm_memo4_1_2_, linkmans1_.lkm_mobile as lkm_mobi5_1_2_, linkmans1_.lkm_name as lkm_name6_1_2_, linkmans1_.lkm_phone as lkm_phon7_1_2_, linkmans1_.lkm_position as lkm_posi8_1_2_ from cst_customer customer0_ left outer join cst_linkman linkmans1_ on customer0_.cust_id=linkmans1_.lkm_cust_id where customer0_.cust_id=?
-->Customer{custId=302, custAddress='null', custIndustry='教育', custLevel='null', custName='zhangxiaoyu', custPhone='null', custSource='null'}

为所有的Repository上添加自定义方法

SpringBoot与SpringDataJPA整合

添加配置

在applicationContext.xml文件中添加配置:

1
2
3
4
5
6
7
8
9
# 数据库驱动可以不用配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/smbms?
useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
# Hibernate根据给定的实体类结构更改数据库中映射的表
spring.jpa.hibernate.ddl-auto=update

其他的参数设置以及参数说明参考官网:https://spring.io/guides/gs/accessing-data-myusql/

Logstash

Logstash

Logstash 介绍

Logstash是具有实时流水线功能的开源数据收集引擎。Logstash可以动态统一来自不同来源的数据,并将数据标准化到您选择的目标位置。清除所有数据并使其民主化,以用于各种高级下游分析和可视化用例。

虽然Logstash最初推动了日志收集方面的创新,但其功能远远超出了该用例。任何类型的事件都可以通过各种各样的输入,过滤器和输出插件来丰富和转换,许多本机编解码器进一步简化了提取过程。Logstash通过利用大量数据和各种数据来加快您的见解。

Logstash入门

ElasticSearch

ElasticSearch

版本:ElasticSearch 7.6.1

6.X 7.X 的区别十分大

我们要讲解什么?

SQL :like %狂神说%,如果是的大数据,就十分慢!索引!

ElasticSearch:搜索!(百度、github、淘宝电商!)

1、聊一个人

2、货比三家

3、安装

4、生态圈

5、分词器ik

6、RestFul操作ES

7、CRUD

8、SpringBoot 集成ElasticSearch (从原理分析!)

9、爬虫爬取数据!

10、实战,模拟全文检索!

以后你只要,需要用到搜索,就可以使用ES!(大数据量的情况下使用!)

easyExcel

easyExcel

谈谈POI 和easyExcel

常用信息

1、将用户信息导出为excel表格(导出数据….),一般导出为xls

2、将Excel表中的信息录入到网站数据库(习题上传….),可以减轻网站的录入量

开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中!

操作Excel目前比较流行的就是Apache POI 和阿里巴巴的easyExcel !

Apache POI

Apache POI 官网:https://poi.apache.org/

第1章Zookeeper理论基础

分布式协调服务器Zookeeper

第3章Zookeeper的安装与集群搭建

安装单机Zookeeper

下载Zookeeper安装包

http://zookeeper.apache.org 官网下载。

上传安装包

将下载的Zookeeper 安装包上传到zk04 主机的/usr/tools 目录。

安装配置zk

解压安装包

image-20200428153544533

创建软链接

image-20200428153713891

复制配置文件

复制Zookeeper 安装目录下的conf 目录中的zoo_sample.cfg 文件,并命名为zoo.cfg。

image-20200428153727493

修改配置文件

image-20200428153736195

新建数据存放目录

image-20200428153745370

注册bin 目录

image-20200428153753283

重新加载profile 文件

image-20200428153801890

操作zk

开启zk

image-20200428153813891

查看状态

image-20200428153821979

重启zk

image-20200428153830076

停止zk

image-20200428153838141

搭建Zookeeper 集群

下面要搭建一个由四台zk 构成的zk 集群,其中一台为Leader,两台Follower,一台Observer。

克隆并配置第一台主机

克隆并配置主机

克隆前面单机Zookeeper 主机后,要修改如下配置文件:
修改主机名:/etc/hostname
修改网络配置:/etc/sysconfig/network-scripts/ifcfg-ens33

创建myid 文件

在/usr/data/zookeeper 目录中创建表示当前主机编号的myid 文件,该编号为当前主机在集群中的唯一标识。

image-20200428153921466

修改zoo.cfg
在zoo.cfg 文件中添加zk 集群节点列表。

image-20200428153932731

克隆并配置另两台主机

克隆并配置另外两台主机的方式是相同的,下面以zkos2 为例。

克隆主机

克隆前面zkos1 主机后,要修改如下配置文件:

  • 修改主机名:/etc/hostname
  • 修改网络配置:/etc/sysconfig/network-scripts/ifcfg-ens33

修改myid

修改myid 的值与zoo.cfg 中指定的主机编号相同。

image-20200428154757235

image-20200428154802362

克隆并配置第四台主机

第四台主机即为要作Observer 的主机。

image-20200428154818698

启动zk集群

使用zkServer.sh start 命令,逐个启动每一个Zookeeper 节点主机。

伪集群的搭建

这里要搭建的集群与前面的集群相同,都由四台服务器组成,其中第四台为Observer。
伪集群的搭建与真实集群的搭建差不多。其主机步骤分为四大步。

复制配置文件

这里需要四个配置文件,都存放在zk 安装目录的conf 目录中。所以它们的文件名肯定是不同的。

image-20200428154858225

修改配置文件内容

以下是第四台Server 的配置文件。

image-20200428154909982

另外三个配置文件中指定的dataDir 分别是/usr/data/zookeeper1、/usr/data/zookeeper2、
/usr/data/zookeeper3。clientPort 分别是2181、2182、2183。当然,另外三个配置文件中不要设置peerType=observer。

创建数据目录

在/usr/data 下创建如下四个目录。

image-20200428154935070

创建myid文件

分别在zookeeper1、zookeeper2、zookeeper3、zookeeper4 四个目录中创建四个myid 文件,内容分别为1、2、3、4。

image-20200428154949543

集群启动

伪集群的启动需要指定每台Server 启动所使用的配置文件。进入到zk 的安装目录。

image-20200428155003357

查看各个Server 的状态。

image-20200428155027041

Spring核心理解

掌握Spring和SpringBoot、SpringCloud等的关联和区别

详细信息去官网Spring.io中查看

Spring家族

Spring基础部分(IOC、DI、AOP),包括SpringBoot和Spring Framework

注:

SpringBoot除了包含Spring基础的一些功能,还包括自动配置、起步依赖(解决的就是使maven更加简洁)、零配置文件(Spring配置文件)

Spring Framework是包含SpringMVC模块的,里面分成很多的组件,这些组件其实就是jar包

Spring企业应用组件,包括SpringCloud、SpringData、SpringSecurity

Spring核心概念

IOC:控制反转,其实new对象的权利由使用者改为Spring去创建,Spring要想使用IOC去创建对象,就必须使用到DI

DI:依赖注入,就是给new出来的对象设置成员变量

AOP:将项目中的业务代码(提交订单)和系统代码(事务、记录日志)进行解耦,也是给业务代码进行功能增强的一种途径,AOP和OOP一样,都是编程思想

AOP的实现有SringAOP、AspectJ、Spring整合AspectJ这3种方式

BOP:指的是面向Bean的编程,Bean指的是无状态的Java对象,在Spring中一切都是针对Bean设计和编程的

Bean被Spring通过IOC和DI进行创建和管理,Bean被Spring通过AOP进行功能增强

Bean其实就是对应bean标签和@Bean注解的类

基础容器:IOC容器、具体指BeanFactory接口,具体是DefaultListableBeanFactory

高级容器:高级容器具体指的就是ApplicationContext接口,实现了BeanFactory接口,只是在该接口之外,新加了一些高级功能,本质还是一个BeanFactory

高级容器和基础容器的区别

高级容器对于bean实例的加载时机是项目启动时(可以理解为饿汉式加载)

基础容器对于bean实例的加载时机是第一次被需要时(可以理解为懒汉式加载)

循环依赖

A->B…B->A

构造方法的依赖,无法解决

1
2
3
4
5
6
7
8
9
10
11
12
13
public A{ 
private B b;
public A(B b){
this.b = b;
}
}

public B{
private A a;
public B(A a){
this.a = a;
}
}

set方法的循环,Spring中可以通过缓存去解决掉

1
2
3
4
5
6
7
8
9
10
11
12
13
public A{ 
private B b;
public setB(B b){
this.b = b;
}
}

public B{
private A a;
public setA(A a){
this.a = a;
}
}

Spring体系结构

EJB框架,JavaBean都是有状态的bean。
spring,提供一套一站式的轻量级企业开发框架。

Spring总共大约有20个模块,由1300多个不同的文件构成。而这些组件被分别整合在核心(CoreContainer)、AOP(AspectOrientedProgramming)和设备支持(Instrmentation)、数据访问及集成(DataAccess/Integeration)、Web、报文发送(Messaging)、Test,6个模块集合中。

以下是Spring5的模块结构图:

image-20200424204334253

image-20200424204351357

组成Spring框架的每个模块集合或者模块都可以单独存在,也可以一个或多个模块联合实现。每个模块的组成和功能如下

核心容器

由spring-beans、spring-core、spring-context和spring-expression(Spring ExpressionLanguage,SpEL)4个模块组成。

spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(InversionofControl,IOC)和依赖注入(DependencyInjection,DI)。BeanFactory接口是Spring框架中的核心接口,它是工厂模式的具体实现。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。但BeanFactory容器实例化后并不会自动实例化Bean,只有当Bean被使用时BeanFactory容器才会对该Bean进行实例化与依赖关系的装配。

spring-context模块构架于核心模块之上,他扩展了BeanFactory,为她添加了Bean生命周期控制、框架事件体系以及资源加载透明化等功能。此外该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext是该模块的核心接口,她是BeanFactory的超类,与BeanFactory不同,ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。

spring-expression模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。这种语言的特性是基于Spring产品的需求而设计,他可以非常方便地同SpringIOC进行交互。

AOP和设备支持

由spring-aop、spring-aspects和spring-instrument3个模块组成。

spring-aop是Spring的另一个核心模块,是AOP主要的实现模块。作为继OOP后,对程序员影响最大的编程思想之一,AOP极大地开拓了人们对于编程的思路。在Spring中,他是以JVM的动态代理技术为基础,然后设计出了一系列的AOP横切实现,比如前置通知、返回通知、异常通知等,同时,Pointcut接口来匹配切入点,可以使用现有的切入点来设计横切面,也可以扩展相关方法根据需求进行切入。

spring-aspects模块集成自AspectJ框架,主要是为SpringAOP提供多种AOP实现方法。

spring-instrument模块是基于JAVASE中的”java.lang.instrument”进行设计的,应该算是AOP的一个支援模块,主要作用是在JVM启用时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现AOP的功能。在分类里,我把他分在了AOP模块下,在Spring官方文档里对这个地方也有点含糊不清,这里是纯个人观点。

数据访问及集成

由spring-jdbc、spring-tx、spring-orm、spring-jms和spring-oxm5个模块组成。

spring-jdbc模块是Spring提供的JDBC抽象框架的主要实现模块,用于简化SpringJDBC。主要是提供JDBC模板方式、关系数据库对象化方式、SimpleJdbc方式、事务管理来简化JDBC编程,主要实现类是JdbcTemplate、SimpleJdbcTemplate以及NamedParameterJdbcTemplate。

spring-tx模块是SpringJDBC事务控制实现模块。使用Spring框架,它对事务做了很好的封装,通过它的AOP配置,可以灵活的配置在任何一层;但是在很多的需求和应用,直接使用JDBC事务控制还是有其优势的。其实,事务是以业务逻辑为基础的;一个完整的业务应该对应业务层里的一个方法;如果业务操作失败,则整个事务回滚;所以,事务控制是绝对应该放在业务层的;但是,持久层的设计则应该遵循一个很重要的原则:保证操作的原子性,即持久层里的每个方法都应该是不可以分割的。所以,在使用SpringJDBC事务控制时,应该注意其特殊性。

spring-orm模块是ORM框架支持模块,主要集成Hibernate,JavaPersistenceAPI(JPA)和JavaDataObjects(JDO)用于资源管理、数据访问对象(DAO)的实现和事务策略。spring-jms模块(JavaMessagingService)能够发送和接受信息,自SpringFramework4.1以后,他还提供了对spring-messaging模块的支撑。

spring-oxm模块主要提供一个抽象层以支撑OXM(OXM是Object-to-XML-Mapping的缩写,它是一个O/M-mapper,将java对象映射成XML数据,或者将XML数据映射成java对象),例如:JAXB,Castor,XMLBeans,JiBX和XStream等。

Web

由spring-web、spring-webmvc、spring-websocket和spring-webflux4个模块组成。

spring-web模块为Spring提供了最基础Web支持,主要建立于核心容器之上,通过Servlet或者Listeners来初始化IOC容器,也包含一些与Web相关的支持。

spring-webmvc模块众所周知是一个的Web-Servlet模块,实现了SpringMVC(model-viewController)的Web应用。

spring-websocket模块主要是与Web前端的全双工通讯的协议。(资料缺乏,这是个人理解)

spring-webflux是一个新的非堵塞函数式ReactiveWeb框架,可以用来建立异步的,非阻塞,事件驱动的服务,并且扩展性非常好。

报文发送

即spring-messaging模块。

spring-messaging是从Spring4开始新加入的一个模块,主要职责是为Spring框架集成一些基础的报文传送应用。

Test

即spring-test模块。

spring-test模块主要为测试提供支持的,毕竟在不需要发布(程序)到你的应用服务器或者连接到其他企业设施的情况下能够执行一些集成测试或者其他测试对于任何企业都是非常重要的。

Spring是如何通过容器创建和管理bean实例

源码阅读篇

Spring重要接口详解

BeanFactory继承体系

体系结构图

image-20200425092736421

image-20200425092749263

这是BeanFactory基本的类体系结构,这里没有包括强大的ApplicationContext体系,ApplicationContext单独搞一个

|