day01:项目简介/搭建项目环境
2-9:实现项目需求
10-12:spring-AOP(ASPECT ORIENTED PROGRAMMING)
事务处理
关联映射
动态SQL
正课:
项目简介
搭建项目环境
spring webmvc+Spring+Mybatis
1.导包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>
扫描多个包
<context:component-scan base-package="cn.tedu,com.tedu"/>
在实际开发中:
凡是带id属性的实体类都必须重写equals和hashCode方法,只需要重写id的equals和hashCode方法
@ResponseBody //调用jackson
public User loadUser(){
User user = new User(1,"Robin",28);
return user;
}
如何使用:
-导包:jackson
-在放回数据的方法上加@ResponseBody
——————————————————————
项目简介:
- 项目概述
>>云笔记,是tmooc上的一个子项目,用于客户进行在线学习记录,分享,收藏笔记,以及参与社区活动.
- 模块划分
>> 用户模块: 登录 注册 修改密码,退出
笔记本模块:创建 删除 更新 查看
笔记模块: 创建 删除 更新 查看 转移
分享/收藏功能:分享 收藏 查看 搜索
回收模块: 查看 彻底删除 恢复
活动模块: 查看活动 参加活动
- 设计思想
- 技术架构
项目规范:
-所有请求使用html
关联关系;
一对一
一对多
多对多
-——————————————————————————————
常用命令
mysql -uroot -t //启动mysql命令行
show databases; //查看有哪些数据库
create database cloud_note; //创建数据库
drop database cloud_note; //删除数据库
use 数据库名; 连接数据库
show tables; 查看有哪些表;
导入SQL文件
set names utf8;
source /home/soft01/cloud_note.sql;
windows系统下:
set names utf8;
source e:\\cloud_note.sql;
E-R 实体-关系模型图
由业务分析产生,作为实体类和数据库设计的依据.
<property name="url" value="jdbc:mysql:///cloud_note?useUnicode=true&characterEncoding=utf8"/>
相当于<property name="url" value="jdbc:mysql://localhost:3306....cloud_note?useUnicode=true&characterEncoding=utf8"/>
//cloud_note是数据库名
//xml里面不能直接写&,而需要转义&
————————————————————
jQuery API 1.11.3 速查表 --作者:Shifone
html登录页面如何获取路径?$.ajax({"url":path+"/user/login.do});中的path怎么来的?
widow.loation.path??
为何UserDao不需要注解userDao?而UserService的实现类又有?
难道因为是dao是使用了mybatis-spring的mapper扫描?加了注解userDao反而报错该bean不存在?
service实现类的User user =userdao.findByName(name); 这里为何空指针异常?
但是测试dao的findByName()方法又正常
正常来说,这里UserDao是有其实现类的,
UserDao u = new UserDao();不可行,因为接口类型指向实现类
好吧,思路是对的。context component接管了bean的生成,如果不指定名字,如@Resource("userDao“)
则会自动生成个第一个字母小写的实例,也就是bean。而对于userDao接口,因为使用了Mybatis,应该是已经生成了该接口的
实现类的了,比如配置了Mapper,写了映射文件。
反正现在是改名重新导入项目结果竟然可以了。
!!!!!!!!!!!
要求:需要复习下从mvc模式,在xml中配置bean,之后的内容
在注册的函数中:$("#warning_1").show(); //这句的作用是什么?
______________________________________________________________________________
day02
之所以继承RuntimeException,就是为了事务处理。只有该异常才能进行事务回滚
jquery.min.js是一个简化的版本
md5 摘要算法
最初用于文本比较
将任意长度的字节处理成等长的结果
不可逆
public static String md5(String src){
try {
MessageDigest md = MessageDigest.getInstance("MD5");
//MD5加密
byte[] output = md.digest(src.getBytes());
//Base64.
return new String(output);
}
Base64
a-z:26
A-Z:26
0-9:10
= +
JsonResult数据格式: {"status":xxx,"data":xxx,"message":xxx}
—————————————————————————————————————
作业:
完成登录功能的服务器处理:
-Controller
-Service
-Dao
在mysql中没有序列这个对象,不同于oracle。
mysql中有一个自增的?uuid
UUID算法:(生成的id不重复
——————————————————
day04:
将try/catch去除,改用 @ExceptionHandler可以统一处理控制器中的异常类型
重复性的异常抽到抽象类中
实现序列话接口
一定有无参
对属性赋值有setter/getter方法
重写toString
重写equals/hashCode
//将bookId绑定到元素中.data为数据缓存
$li.data("bookId",bookId);
2.尝试点击笔记本事件绑定
父元素.on("click","元素",Function());
3.点击笔记本记录,显示被选中的效果
事件绑定:
$("#book_ui li").click(function(){
alert("绑定成功");
})
在做绑定的时候,
$(function(){
//加载笔记本列表
loadBooks();
});
页面的loadBooks还没加载完。loadBooks发送请求完成后就执行
$("#book_ui li").click(function(){了,而这时候服务器还没返回笔记本数据
ul已经存在,但是li还没有。
因此使用
父元素.on("click","li",fn);的监听方式
如
$("#note_ui").on("click","li",fn);
________________________________________________________________
//监听笔记本li元素的单击事件
$("#book_ul").on("click","li",function(){
//获取参数bookId
var bookId=$(this).data("bookId");
console.log(bookId);
//发送ajax请求
});
controller的传参的参数名也就是浏览器发给服务器的参数名!!!!
List<Map<String,Object>>
key:字段名,或者是as以后的字段名
Mapper.xml复制之后记得更改id,最好sql语句全部删除,否则容易出错
如果总是invalid statement,那就是id名没改回来
——————————————————————————————————————————
//先清除
$("#book_ul a").removeClass("checked");
//$("#book_ul a").remove("checked");
//设置选中效果//选中当前的,找到a,加上属性Class
$(this).find("a").addClass("checked");
//下面这句的效果:鼠标移出之后就失去效果了,但是上面那句不会,效果会保留
//$(this).find("a").addClass(".checked");
——————————————————————————
//在开始本次之前把上一次的清除
//$("#note_ul").empty();
//上下两句效果相同
$("#note_ul li").remove();
————————————————————————————————————
富文本编辑器
ue
UEditor
怎么用?
1.引入组件的js文件
2.Script代码中生成实例
var um = UM.getEditor('myEditor');
3.通过id,将组件防止到预期的位置
<!--- 笔记标题 --->
<div class="row">
<div class="col-sm-12">
<!--- 输入框 --->
<script type="text/plain" id="myEditor" style="width:100%;height:400px;">
</script>
<!--- 输入框 --->
</div>
</div>
4.通过set/get方法操作组件
-setContent
-getContent
select cn_note_title as title,cn_note_body as body from cn_note where cn_note_status_id='1' and cn_note_id=#{noteId}
——————————————————————————————————
分页:
select * from(
select c.*,rownum r from
(
select * from cost order by cost_id
)c
where r between #(start) and #{end}
)
为什么测试方法总会报空指针异常?
因为service层的方法return 还是null啊
还是null啊
还是null啊
还是null啊
还是null啊
还是null啊
还是null啊
dao使用int update(Note note)
在映射文件Mapper中的select不支持返回值类型resultType
mybatis不支持传多参数,
如需要可以传对象,如果是无关的参数可以传map
————————————————————————————————————————————————————
day07
二进制和反射
-n=~n+1
~取反
>>> >> <<移位运算
计算机中位运算性能比乘法运算好
>>>和>>的区别
>> : 数学右移位运算,负数(高位为1的数)高位 补1,正数补0
>>> :逻辑右移位运算,无论正负都补0
i=10010010 00010101 11110100 10110000
n=i>>>1
n=010010010 00010101 11110100 1011000
m=i>>1
m=110010010 00010101 11110100 1011000
>>>:应用案例:
IO流
网络/磁盘写入单位是1个字节
发送:
i=10010010 00010101 11110100 10110000
b1=10010010
b2=00010101
b3=11110100
b4=10110000
& 与运算: 逻辑乘
面试题:
优化n%8为(n&7)
n%8 和 n&7都是对8取余,有什么区别:
15%8=7
18%8=2
...[0 , 8)
不管任何数对8取余都不超过8
模运算其实就是二进制的与运算。
n=20=00010100
m=7 =00001111
-------------------
与运算就是截取后面的位数,如8则截取低三位,最大余数是7
——————————————————————————————————————————
//& 与运算: 逻辑乘
/*
* 0&0=0
* 0&1=0
* 1&0=0
* 1&1=1
*/
//将00001001010000100111010010111001切割成4份
int i=0x94274b9;
//m的11111111相当于孔洞,把每次8位漏下来
// 一个f叫做4个掩码。如ip地址配置的子网掩码
//192.168.1.1
//8位/8位/8位/8位
//255.255.255.255.0
//192.168.23.11/24中的24表示24个1,也就是掩码是24,如这里的m,f就是4个1
int m= 0xff; //Mask
int b1=i&m;
int b2=(i>>>8)&m; //掩码运算
int b3=(i>>>16)&m;
int b4=(i>>>24)&m;
try {
RandomAccessFile raf = new RandomAccessFile("config","rw");
raf.writeInt(4);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
print(i);
print(m);
System.out.println("将一个4字节的int数切割4份:");
print(b1);
print(b2);
print(b3);
print(b4);
System.out.println("拼接后的结果:");
//组装:b4左移24位,b3左移16,b2左移8,b1左移0.然后数学+即可
int result = (b4<<24)+(b3<<16)+(b2<<8)+b1;
print(result);
System.out.println("源数值和拼接结果是否一致:");
System.out.println(i==result);
//或运算。|相当于加法
/*
* 0|1=1
*
*/
/*
* RandomAccessFile源代码中:
* public final int readInt() throws IOException {
int ch1 = this.read();
int ch2 = this.read();
int ch3 = this.read();
int ch4 = this.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
*
*
*
* raf的read()方法,-1表示读到文件末尾
* ch1 =00000000 00000000 00000000 11100101
* ch2 =00000000 00000000 00000000 11001101
* ch3 =11111111 11111111 11111111 11111111
* ch4 =11111111 11111111 11111111 11111111
* |——————————————————————————————————————
* 11111111 11111111 11111111 11111111
*因此源代码中这句判断 if ((ch1 | ch2 | ch3 | ch4) < 0)
*只要有一个字节是全1,则进行或运算之后都是-1,表示读取到了文件末尾
*/
__________________________________________________________________________________________________________
对象的序列化:把对象的切割成一组组二进制
二进制上一层:方法
————————————————————————————
反射:
java中动态执行api,反射api可以:
动态创建对象,动态调用方法,动态访问属性
动态执行:
静态执行:经过编译确定执行次序,在运行期间按照编译次序执行
JAVA 中的动态执行: 运行期间才能确定加载哪些类, 创建哪些对象, 执行哪些方法...
动态加载类,在程序执行期间动态获得类名,根据类名加载类
API
Class cls = Class.forName(类名);
动态创建对象
Object obj = cls.newInstance();
//动态创建对象.要求类必须有无参构造器,无则抛异常
动态读取属性
Field fld
fld.get();
步骤:
加载类
找到类中的属性声明信息Field对象(属性是属于对象的)
创建对象
读取对象的属性值
面试:怎样在对象的外部读取一个对象的私有属性?
反射不仅能够访问普通属性,还能访问私有属性。
注意:需要在访问之前打开,使用fld.setAccessible(true);
System.out.println("输入属性名:");
String name=in.nextLine();
//动态在cls代表的类信息上找到属性信息
Field fld = cls.getDeclaredField(name);
//属性值=fld.get(被访问对象); fld的属性必须跟obj一致
//obj="abc"; //用来测试因为field上对应的属性不一致而报异常
//读取属性之前使用setAccessible方法就可以访问私有属性了
fld.setAccessible(true);
Object val = fld.get(obj);
System.out.println(val);
虽然注解使用了私有属性,如
@Resource
private NoteService noteService;
spring也是用了反射
反射是对封装的破坏,不能乱用
访问私有属性只是反射的一个功能。
所有的框架,底层都是反射。spring/mybatis/junit都是
——————————————————————————————————————————————
day07
二进制在09的集合里面
反射在spring的拦截器
反射
java提供的动态
System.out.println("Input: ");
String className = in.nextLine();
//Class是反射API的入口
Class cls = Class.forName(className);
//cls引用了类的相关信息.getDeclaredField返回一堆属性信息
Field[] field = cls.getDeclaredFields();
for(Field f : field){
System.out.println(f);
}
//可以获取类中全部的方法信息.包括父类的
Method[] method = cls.getMethods();
for(Method m: method){
System.out.println(m);
}
Junit3中一个方法名为test开头,就作为测试运行
反射的用途:
案例:执行一个类中全部以test为开头的方法,这些方法都是无返回值的无参的。如果不用反射,没法写,因为不知道类名,
不知道方法名,只知道方法名特征,比如以test开头,其他都不知道
工作中,遇到被执行的类,被执行的方法不知道是哪个方法哪个类,用反射
解决方案:
1.动态加载一个类
2.动态找到全部方法信息
Method[] method = cls.getMethods();
for(Method m: method){
System.out.println(m);
}
3.遍历方法信息,检查方法名是否以test为开头。用正则或者startsWith过滤
String有哪些api
map有哪些api
4.执行这个方法
for(Method m : method){
//System.out.println(m);
//在方法信息上获取方法名
String name = m.getName();
//检查方法是否以test为开头的
if(name.startsWith("test")){
System.out.println(name);
//执行/调用(invoke)方法
m.invoke(obj.args); //对象所拥有的方法
}
invoke重点!!
如Spring,xml中告知类名即可
注解也是。注到方法,属性要动态获得其方法和属性
动态加载类
Class.forName
获取方法
Method invoke
junit4:查询一个类中所有方法,如果方法包含了注解,就加载
利用?????/
TestCase中编译完@Test就被擦除了
SOURCE:注解只在源代码存在
类加载注解就被删除
反射实在运行期的,而注解已经在编译完就被擦除了
@Retention(RetentionPolicy.RUNTIME) //编译完了注解有,放到方法区,这样运行期也还有注解
//@Retention(RetentionPolicy.SOURCE) //注解只在源代码中
//@Retention(RetentionPolicy.CLASS) //注解只保留在类中
public @interface Test {
class是单例的:也就是在内存中只有一份
单例模式:
1.构造方法私有化,防止在本类外实例化对象
2.声明一个本类对象
3.给外部提供一个静态的方法获取对象实例
饿汉式:不管用不用,先创建
懒汉式:要用才创建
/**
* 单例模式的解释
*/
public class Demo04 {
public static void main(String[] args) {
//Girl g1 = new Girl();
//Girl g1 = new Girl();
//Girl g = Girl.one;
//Girl.one = null;
Girl g1 = Girl.getOne();
Girl g2 = Girl.getOne();
Girl g3 = Girl.getOne();
}
}
class Boy{
private static Boy one; //懒汉式,one是空的
private Boy(){
}
public synchronized static Boy getOne(){ //假如并发调用呢?多线程访问get方法,
if(one==null){ //这里是读
//按需加载,不用则不创建
//这里是写。读写一起会出现线程安全问题。除非加同步关键字synchronized
one=new Boy();
}
return one;
}
}
/**
* 不管用不用都先创建Girl,这个是饿汉式/非懒汉式的单例模式
*/
//私有的one+只读的get方法就不能被修改了,这个类的结构即单例模式,永远保证只有一个
class Girl{
//Girl类型的属性,但是获取属性要先有对象。但是还需要static,否则也还是拿不到
private static Girl one = new Girl(); //static Girl one类加载器只有一个,不需要加同步关键字也可以
private Girl(){
}
public static Girl getOne(){
return one; //这里是只读
}
}
java中流就是23中模式中的装饰器模式
工厂模式:多次反复创建对象的时候。再需要对象只需要调用方法。用来解决反复创建对象
MVC:在桌面系统中是用观察者模式解决的
工厂模式:多次反复创建对象的时候。再需要对象只需要调用方法。用来解决反复创建对象
————————————————————————————————————
day09
查询被分享的笔记.
select n.cn_note_id,n.cn_note_title from cn_note n JOIN (select * from cn_share)s ON n.cn_note_id=s.cn_note_id
————————————————————————————————————————————
————————————————————————
check SecurityInsurance
copy RepositoryProcedure
take SecondMask
————————————————————————————————————————
搜索分页显示
select * from cn_share where cn_share_title like '%java% limit n, m
n:抓取数据的起点,mysql中从0开始的,0代表第一条而Oracle是从1开始的
m:每页所显示的最大数
10条记录,每页最多显示3条,则m=3,最后一页只有1条
——————————————————————————————
select * from cn_share like %关键词%
select * from cn_share like #{title}
limit #{begin},3
m:记录的位置
n:每页显示的最大记录数
page1 启始位置:0
page2 启始位置:3
page3 启始位置:6
n (n-1)*3
“更多”按钮是基于回车的,同一个结果集
————————————————————————————————————————————————————
day10
#spring AOP
##AOP概念
aspect oriented programming
案例:
新注册用户赠送80,此80为广告促销预算。假设100万或者5万预算。预算花到头就必须停止。促销的商品加价,总计加价50万。
注册账户,同时在用户表增加一条记录,同时也在账户表增加50元,而且总促销账户减50.推荐人账户加20。
多张表只能是一起加减或者一起不加。因此需要事务控制,要么成功,要么不成功。
利用aop可以实现事务
面向切面编程
##是什么?
将共同的业务处理从传统业务处理中抽离出来,单独封装,
然后以配置的形式进行关联
都是对service层下的操作
把每一个方法当作一个面
对系统service层进行AOP处理
对系统service下所有方法追加相同的功能处理
案例:
如在每一个方法增加执行时间统计,方法开始执行的时间,方法结束的时间。
使用AOP只需要写一个方法,作用到每一个方法上,而不需要在每一个方法上都增加代码
如某段时间需要加上性能监测,而性能改进之后,项目迭代之后,需要撤销性能监测。
所以需要AOP
————————————————————————
long t1;
利用反射,调用业务方法
long t2;
t2-t1;
————————————————————————
分三个步骤:
1.导包。AspentJ
2.创建切面组件。aspect包,以及类如AspcetDemo
AspcetDemo启用注解@AspcectJ
@Component
配置spring-aop.xml
<!-- 启用aop扫描 对@Component生效-->
<context:component-scan base-package="cn.tedu.cloudnote.aspect"></context:component-scan>
<!-- 启用aop注解对@AspcectJ生效-->
<aop:aspectj-autoproxy/>
3.配置切面组件,作用到需要的地方
案例:在每个controller上增加打印信息
1.导包:
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
2.
<!-- within为类限定表达式 -->
<aop:before method="logController"
pointcut="within(cn.tedu.cloudnote.controller..*)"/>
其中..包和子包以及子包下的所有方法
--注解方式实现:
@Component @Aspect @Before
#AOP相关概念
- OOP:类,对象,封装,继承,多态
- AOP:切面,切入点,通知,动态代理
##切面(aspect)
指的是封装了共同处理的组件,并且能够切入到其他组件的方法上.
##切入点(pointcut)
"within(cn.tedu.cloudnote.controller..*)"
用于指定目标组件的方法.切入点要加入到哪个组件上
- 方法限定表达式》》》》
可以切入到具体的方法上
可以给某个组件中部分方法追加共同功能
execution(修饰符? 返回类型 方法名(参数) 异常抛出?)
//匹配到add开头的所有方法
execution(* add*(..)) //不管返回值,不管参数列表是什么,只要是add开头的方法都匹配
execution(void add*(..)) //只要是add开头的,而且没有返回值的方法都匹配
//匹配UserService包下的所有方法
execution(* cn.tedu.UserService.*(..))
//匹配service包下所有类的所有方法
execution(* cn.tedu.service.*.*(..))
//匹配service包及子包下的所有方法
execution(* cn.tedu.service..*(..))
——————————————————————————————————-
execution在相关设定的时候替换了within
————————————————————————————————————
- 类型限定表达式》》》》
可以给某个组件的所有方法追加功能
within(类型)
//匹配UserService组件下所有方法
within(cn.tedu.service.UserService)
//匹配service包下所有类的所有方法
within(cn.tedu.service.*)
//匹配service包及子包下的所有方法
within(cn.tedu.service..*)
——————————————————————————————————
- bean名称限定表达式》》》》
可以给某个组件中所有的方法追加功能
bean(id名)
//匹配id=userService的组件的所有方法
bean(userService)
//匹配以Service结尾的所有组件的所有方法
bean(*Service)
————————————————————————
@Before("bean(userController)")
##通知
用于指定切入的时机
spring提供了五种通知类型
try{
前置通知<aop:before>
//执行的目标方法
后置通知<aop:after-returning>
}catch{
异常通知<aop:after-throwing>
}finally{
最终通知:<aop:after>
}
环绕通知:
@around=前置+后置通知
注意:环绕通知跟其他4个的方法不同,返回Object的有参的,如下的性能审计。
——————————————————————————————————————————————————————————
@Around("within(cn.tedu.cloudnote.service..*)") //service包下所有子包所有类的所有方法
public Object log(ProceedingJoinPoint point) throws Throwable{
Object obj = new Object();
try {
long start = System.currentTimeMillis();
obj= point.proceed(); //代表具体执行的方法,返回的是一个对象
long end = System.currentTimeMillis();
//需要获取方法名,否则测试的方法太多不知道是哪个方法的时间
//签名:返回的是一个对象,包含方法名和参数类型.
//导包apectj.lang.Signature
Signature s = point.getSignature();
System.out.println(s+"耗时:"+(end-start));
} catch (Throwable e) {
e.printStackTrace();
throw e;
}
return obj;
一个类中方法签名是唯一的。
按理说java反射应该有,但是却没有。按照我们编程的理解,一个概念一定是有一个对象的
————————————————————————————————————
- 切面: 追加啥功能? 单独封装的代码
- 切入点: 切谁? 方法/类型/bean表达式,如所有Controller
- 通知: 啥时候切? 前置/后置/环绕
##动态代理
AOP原理:底层使用动态代理技术
动态代理技术:动态创建类的技术
动态代理技术有两种:
- 基于接口技术:实现接口,重写接口中的方法
public class $Proxy25 implements 目标接口{
//事务处理
login() //将AOP的事务处理和login一起封装了
}
- 基于目标技术:继承类后,重写类方法
可以创建一个新的类型,重写目标接口或目标类的方法
在重写方法中,追加了要切入的功能代码和方法代码
spring有两种动态技术:
- 基于目标接口的
- 基于目标类的
public class $Proxy22 implements 目标接口{
public void checkLogin(){
//追加了事务处理
//重写了UserServiceImpl.checkLogin
}
}
public class $Proxy22 extends 目标类{
}
#AOP注解配置
##注解标记
- @Component 起到应以<bean>的作用
- @Aspect <aop:aspect ref="loggerBean">
- @Before <aop:Before pointcut=within()>
案例:生成异常日志
- 要求:当系统发生service异常,将异常信息写入日志文件
- 切面:将异常信息写入文件
- 切入点:after-throwing("within(service..*)")
AOP生成的新的类型:重写了service接口的方法,注入了AOP后的
com.sun.proxy.$Proxy25
————————————————————————————————
案例:实现性能审计
--切面:输出消耗时间
--切入点:所有service下的所有方法
--通知:@Around
______________________________________________________________________________________________
案例:异常信息写入日志
- 要求:当系统发生service异常,将异常信息写入日志文件(AOP实现)
- 切面:将异常信息写入文件(FileWriter--PrintWriter)
- 切入点:service层面的所有方法("within(service..*)")
通知:@after-throwing
————————————————————————————————————————————
回顾9个对象和作用域:
page:
page,pageContext,config,exception,response.out(Jspwriter)
request
request
session
session
application
application
作业:
创建切面组件使用bean/方法/类型限定表达式指定切入点
独立完成云笔记service层的性能审计
————————————————————————————————
day11
事务管理:
什么是?
事务:程序为了保证业务处理的完整性,执行的一条或多条SQL语句。
事务管理:对事务中的SQL语句进行提交或者回滚
为什么要使用:
确保数据库数据的完整性,不出现脏数据
A B账户间的转账
每一个dao都当成一个事务
1. 配置Spring-transaction.xml
2. 使用@Transactional标记
事务处理只能是RuntimeException
编程式事务管理
try{
业务SQL1;
业务SQL2;
业务SQL3;
conn.commit();
}catch(Exception e){
conn.rollback();
}
声明式事务管理(使用注解,配置)!!!!!!!!!重点!!!!!!!!!!!!!!!!!
案例:批量删除使用变长的参数
如:
deleteNotes(String... args);
——————————————————————————————
##可读可写:readonly
作用于select语句的事务上
语法:
select操作时,可采用只读事务
@Transaction(readOnly=true)
——————————————————————————————————————————————
##回滚特性
默认RuntimeException回滚,其他异常不回滚。当遇到其他异常,也需要回滚的时候,就用到该roolBackFor属性
如上传头像和注册,如果上传头像出现了异常,则把信息回滚,此时该异常不是runtimeException,属于IO异常,
否则即便头像出错了,信息也已经写入到持久层了
@Transaction(rollbackFor=IOException.class)
public void f1(){
//db操作(insert)
//IOException
}
}
——————————————————————————————————————
##传播特性
默认类型:REQUIRED
##Spring中常用事务类型:
REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。------》》》默认值
SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。
@TRANSACTIONAL
public void f1(){
//业务代码A
f2(); //方法1调用了方法2.默认值REQUIED的时候:当方法2的业务C出错则影响的f1。把f2看作是f1的业务,只要f2出错了,f1也回滚。工作中都采用默认值,也是最常用的处理方式。如果有其他需求,不会在数据库属性上处理,而是在代码上处理
//业务代码B
}
@TRANSACTIONAL(propagation=REQUIRES_NEW)
public void f2(){
//业务代码C //报错
}
————————————————————————————————————————
##隔离特性——————》为了解决并发处理而设计的。
默认属性:READ_COMMITED
针对事务并发进行处理.
脏读-------->>
事务1进行了增删改DML操作,但并未提交,此时事务2读取了事务操作的数据。此时,事务1进行了回滚,则事务2进行了一次脏读的操作
幻读-------->>事务1在一定范围内查询数据,同时事务2....
(理解:事务1查询了表中全部的数据,在操作的同时,事务2进行了批量插入操作,此时事务1查询的数据变成了一部分,称为幻读)
级别越过,越限制并发操作,如序列化操作的并发为0,安全最高,性能最低。
如级别最低的数据并发经常发生。
读已提交:默认值。兼顾安全和性能
-READ_UNCOMMITTED 读未提交(级别最低)
-READ_COMMITTED 读已提交
-REPEATABLE_READ 可重复读
-SERIALIZABLE 序列化操作(级别最高)
-DEFAULT 根据数据库自动选择READ_COMMITTED
?或REPEATABLE_READ
什么是数据库的事务?
:事务(Transaction)是数据库运行中的一个逻辑工作单位,是用户定义的一个罗辑操作。这些操作要么不成功,要么都成功,是一个不可分割的工作单位。通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。
(2):事务通常是以begin transaction开始,以COMMIT或ROLLBACK结束。
COMMIT表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据库的更新写回到磁盘上的物理数据库中去,事务正常结束。
ROLLBACK表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有以完成的操作全部撤消,滚回到事务开始的状态。
(3):事务运行的三种模式:
A:自动提交事务
每条单独的语句都是一个事务。每个语句后都隐含一个COMMIT。
B:显式事务
以BEGIN TRANSACTION显式开始,以COMMIT或ROLLBACK显式结束。
C:隐性事务
在前一个事务完成时,新事务隐式启动,但每个事务仍以COMMIT或ROLLBACK显式结束。
(4):事务的特性(ACID特性)
A:原子性(Atomicity)
事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。
B:一致性(Consistency)
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
C:隔离性(Isolation)
一个事务的执行不能被其他事务干扰。
D:持续性/永久性(Durability)
一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
注:事务是恢复和并发控制的基本单位。
——————————————————————————————————————————————
关联映射:
将数据库的数据关联到实体的属性
实体对象的引用反应了它们之间的关联关系
表与表直接的关系!
什么是??通过数据库对象之间的关联关系,反映到实体对象之间的引用。
多表查询的时候会用到。
如:通过id查询到用户下的所有笔记,加载到实体类中
#Mybatis关联映射
什么是?将数据库中有关联关系的表,以实体对象引用的方式体现出来
WHY?
加载多个表中的关联数据,封装到实体对象中
关联方式:
- 单个对象关联
cn_user--->User
cn_notebook--->Book
需要把另一个对象作为属性加入到另一个实体对象中
如:
在Book实体类中,增加对象属性private User user;
- 多个对象关联
class User{
private List<Book> books;
}
单个对象关联
class Book{
private User user;
}
什么时候用?
业务需要对数据库进行关联查询的时候.
可以通过一条SQL语句完成关联查询,也可以通过两条SQL语句进行关联查询
##案例:通过userId查询用户信息和关联的笔记本信息
1. User 实体类
2. 定义Dao接口,配置Mapper文件
3. 定义测试类验证查询结果
2个SQL语句: 语句简单,但配置繁琐,与DB两次交互
1个SQL语句: 语句复杂,配置较简单,与数据库交互一次
##案例:通过查询笔记信息,关联用户信息
用一条查询语句实现
#主键的字段处理
在数据库使用自增列或序列作为主键值时,如何在insert执行后,立刻获取ID值.
-mysql
create table t_emp(id int primary key auto_increment, name varchar(20),age int)
User实体类增加Book类型的属性后跟数据库表无法匹配,因此xml不能用resultType="cn.tedu.cloudnote.entity.User">
而是用resultMap
resultMap type="cn.tedu.cloudnote.entity.User" id="userMap1">
<id/> //该id是做主键映射配置的
association:关联单个对象的时候用
collection:关联多个对象的时候用,也就是集合
<resultMap type="cn.tedu.cloudnote.entity.User" id="userMap1">
<!-- 按名称对应装载,可省略 -->
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="books"
javaType=""
ofType="" //集合当中的类型
select="" //这个集合是谁查出来的。先预定义,后面再写
column="" //条件是哪个字段。传进来的值去和数据库中的哪个字段匹配
></collection>
</resultMap>
关联原理是?????????????????
<resultMap type="cn.tedu.cloudnote.entity.User" id="userMap1">
<!-- 按名称对应装载,可省略 -->
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="books"
javaType="java.util.List"
ofType="cn.tedu.cloudnote.entity.Book"
select="findBooks"
column="id"></collection>
</resultMap>
<!-- 查询笔记本表 -->
<select id="findBooks" parameterType="String"
resultType="cn.tedu.cloudnote.entity.Book">
————————————————————————————————————————————
从cn_user表和cn_notebook表中查出用户名下的笔记:
两张表通过user_id关联
select u.cn_user_name, n.cn_notebook_name from cn_notebook n join cn_user u on u.cn_user_id=n.cn_user_id where u.cn_user_id='48595f52-b22c-4485-9244-f4004255b972';
updateUser表的Token
登录时更新用户的Token
if(user.getPassword().equals(md5)){
String token = UUID.randomUUID().toString();
user.setToken(token); //虽然只设定一个字段,实际全部更新了.set返回后有了新的token,带到cookie
//也可通过map做部分更改
Map<String,Object> userInfo = new HashMap<String,Objcet>();
userInfo.put("id",user.getId());
userInfo.put("token",token);
userDao.updateUser(userInfo);
return user;
}
——————————————————————————————————————————————————————
动态sql
mysql也有个自增列,mysql自己从列表的直接生成的
MySql, DB2, SQLServer 等数据库都提供了自增类型, MyBatis提供了这种类型的支持.
创建 表:采用自增列作为主键
create table t_emp (id int primary key auto_increment, name varchar(20),age int);
练习:Mybatis自动获取主键值
1.创建Emp类
2.Dao接口定义save(Emp e)方法
3.Mapper定义<insert useGeneratedKeys="true" keyProperty="id" parameterType="" resultType="">
</insert>
4.创建测试类
注意:
useGenerated
而不是
useGenerate
Oracle数据库的自增ID设置
<insert id="save1">
<selectKey order="BEFORE" resultType="int" keyProperty="id">
select emp_seq.nextval from dual
</selectKey>查出序列之后oracle才会真正执行
insert into emp(id,name,age)values(#{id},#{name},#{age}) 这里的id就是上面kp的id
</insert>
——————————————————————————————————————————————————
MyBatis提供了丰富的动态SQL拼接功能:
<if test=""></if>
<trim>
<set>
<where>
<choose> <when>
<foreach>
————————————————————————————————————————————
choose
业务:
模糊查询笔记:
有title 参数则 title like ?
有body 参数则 body like ?
有key 参数则 title like ? and body like ?
如何实现?
<if test="" > </if>
<choose> 跟java的Switcher一样
<when test=""></when>
<when test=""></when>
<otherwise><otherwise> 相当于java的Switcher的default
</choose>
<trim>
<set>
<where>
<foreach>
————————————————————————————————————————
<select id="findNotesByParam"
parameterType="map"
resultType="map">
select
cn_note_id as id,
cn_note_title as title,
cn_note_body as body
from
cn_note
where
<choose>
<when test="key != null">
cn_note_title like #{key} and
cn_note_body like #{key}
</when>
<when test="title != null">
cn_note_title like #{title}
</when>
<when test="body != null">
cn_note_body like #{body}
</when>
</choose>
order by
cn_note_last_modify_time desc
</select>
————————————————————————————————————————————————————
输入了笔记名称进行搜索的时候
<insert id="">
select * from cn_note
where cn_note_status_id=1
加一个过滤条件,输入了笔记本名称进行搜索的时候
<if test="bookName!=null">
and cn_notebook_name=#{bookName}
</if>
再加一个过滤条件,输入了笔记名称进行搜索的时候
<if test="noteName!=null">
and cn_notebook_name=#{noteName}
</if>
如果再加一个过滤条件,输入了用户名称进行搜索的时候,则继续拼接条件语句
<if test="userName!=null">
and cn_notebook_name=#{userName}
</if>
</insert>
————————————————————————————————————
<where>的用法,代替where.
当下面第一个条件不满足的时候,sql不就变成了这样吗?
select * from cn_note where and cn_notebook_name=#{noteName}
这样多出了“and”
注意:
不仅可以去除无用的前缀后缀。如update value出现多余,
号的时候,where能够去除,如空格
<insert id="">
select * from cn_note
<where> cn_note_status_id=1
<if test="bookName!=null">
cn_notebook_name=#{bookName}
</if>
<if test="noteName!=null">
and cn_notebook_name=#{noteName}
</if>
如果再加一个过滤条件,输入了用户名称进行搜索的时候,则继续拼接条件语句
<if test="userName!=null">
and cn_notebook_name=#{userName}
</if>
</where>
</insert>
——————————————————————————————————————————————————
<set>作用跟where一样,代替了set,在update语句中。update emp set(empname=?) where empid=1
<set>
empname=?
</set>
————————————————————————————————————————————————
<trim>当执行语句没走最后一个条件的时候,如下面的第三个条件未执行,则语句多出了,
<if test="bookName!=null">
cn_notebook_name=#{bookName},
</if>
<if test="noteName!=null">
and cn_notebook_name=#{noteName},
</if>
<if test="userName!=null">
and cn_notebook_name=#{userName}
</if>
trim的使用演示:
<select id="">
update cn_note
set
<!-- 前缀prefix后缀suffix -->
<trim prefix="" prefixOverrides="and / or"
suffix="" suffixOverrides=",">
<if test="bookName!=null">
cn_notebook_name=#{bookName},
</if>
<if test="noteName!=null">
and cn_notebook_name=#{noteName},
</if>
如果再加一个过滤条件,输入了用户名称进行搜索的时候,则继续拼接条件语句
<if test="userName!=null">
and cn_notebook_name=#{userName}
</if>
</trim>
</select>
——————————————————————————————————————
<foreach>的使用
<delete id="deleteNotes"
parameterType="map">
delete from
cn_note
where
cn_note_id in
<foreach collection="idList"
item="id"
open="(" separator="," close=")">
#{id}
</foreach>
</delete>
——————————————————————————————————————————————
void deleteNoteById(String...ids);
<delete id="deleteNotes" parameterType="String">
delete from cn_note where cn_note_id = #{id}
<foreach collection="list/array" //固定写法?
item="id" //别名,如id
//结构的处理从哪个符号开始,到哪里结束,用什么分割
open="("
close=")"
separator=",">
#{id}
</foreach>
</delete>
————————————————————————————————————————————
作业:完成组合查询笔记功能
-service处理
-controller处理
-回调处理
——————————————————————————————————————————————
String常用方法:
charAt
contains
concat
compareTo
compareToIgnoreCase
endsWith
equals
getBytes
format
indexOf
lastIndexOf
matches
replace
replaceAll
split
startsWith
subString
toCharArray
toLowerCase
valueOf
trim
isEmpty
> 大于号>
<小于号,< less than
空格
&; &符号
——————————————————————————————————————————————————————
update cn_note
set
<trim suffixOverrides=","> //有时候不加trim也能工作,但最好加上
<if test="title!=null">
cn_note_title=#{title},
</if>
<if test="body!=null">
cn_note_body=#{body},
</if>
<choose> //if-else语句。choose可以nested
<when test="time!=null">
cn_note_last_modify_time=#{time}
</when>
<otherwise>
cn_note_last_modify_time=unix_timestamp()
</otherwise>
</choose>
</trim>
where
cn_note_id=#{noteId}
当错误变成where
cn_note_id==#{noteId}则会输出sql
——————————————————————————————————
select unix_timestamp //系统时间 跟oracle的sysdate一样。不同的数据库完全不同
foreach:循环,大批量。如批量删除笔记
set
where
if
trim
<choose>
<when>
</when>
<when>
</when>
<otherwise>
</otherwise>
</choose>
____________________________________________________________________________________
web向服务器发送数组:
key是一样的即可
<where><set>
<where>能够去除and,但是如果不行的时候需要另外使用<trim>处理and
——————————————————————————————————————————
事务:
登录:
验证用户名和密码
登录时间,在线时长,登录次数,登录时间段。给对应的登录积分。
发表评论,需要积分
一个网站用到事务很多,都用try/catch明显工作量大,因此需要aop
利用aop,在业务开始的地方,开启事务。
加了@Transactional,就自动执行以下代码
try{ try{
//开启事务<---------------------------------------------------//@Before
@Transactional//加到业务层上
处理事务过程 处理事务过程
CRUD。。。 CRUD。。。
//提交事务<---------------------------------------------------//@AfterReturning
}catch(){
//回滚事务<---------------------------------------------------//@AfterThrowing
}finally{
//释放资源<---------------------------------------------------//@Return
}
声明式事务:利用aop处理事务
加了@Transactional,方法即套用了try/catch
————————————————————————————————————————————————————
方法的调用栈:一层调用一层。看控制台从下往上的异常可见是调用层级
java.lang.RuntimeException: 删错了
at cn.tedu.cloudnote.service.NoteServiceImpl.deleteBatchNotes(NoteServiceImpl.java:169)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
AOP拦截器。在执行deleteBatchNotes业务方法的时候,
通过反射先执行AOP也就是切面组件,这时候是不是执行业务方法呢?不是,而是先执行业务组件
也就是切面组件,切面组件执行完了,再利用反射调用了业务方法
at cn.tedu.cloudnote.aspect.AuditBean.log(AuditBean.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
Spring的AOP,AOP是动态代理做的,最底层就是反射
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy27.deleteBatchNotes(Unknown Source)
上下可见动态代理,aop的底层 测试方法调用了deleteBatchNotes方法,而且是调用了动态代理。
deleteBatchNotes调了JdkDynamicAopProxy.
at cn.tedu.cloudnote.test.TestNoteService.delleteNotes(TestNoteService.java:113)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) //c语言调用,java底层
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) //java底层
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) //java底层
at java.lang.reflect.Method.invoke(Method.java:606) //反射的方法调用
上下可见junit调用了反射。可见invoke
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
junit底层就是反射做的
junit调用反射,反射调用测试方法,测试方法运行,测试方法调用delelenotes方法
aop切面组件执行完再利用反射调用业务的delete方法
业务方法--》tran---》aop---》aop异常拦截---》抛异常---》事务回滚
业务方法--》tran---》aop---》aop异常拦截---》不抛异常---》事务无异常正常执行