什么是事务?
百度百科的解释
事务是指为单个逻辑单元执行一系列的操作,要么完全地执行,要么就不能执行。
维基百科的解释
数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
逻辑单元
这两个解释不太一样,但都提到逻辑单元。
就我们所知道的,在计算机中有很多指令,每一条指令都构成一个执行单元,如果那一条指令出现问题,可能都会造成系统的瘫痪。当然,数据库的事务也不例外。
我们在执行一条插入数据语句,或者更新数据和删除数据语句时,这都代表一个事务,为什么我们在平常的操作的中,没有感觉到呢?因为,在数据库的世界中,如果我们没有开启事务时,数据库就会默认执行这条事务。当然,这在一定程度上,是不大安全的,为什么这么说呢?
比如我们在进行转账事务时,如果用户A给用户B转100块钱时,这就涉及到两个事务,一个是用户A的钱数减少,一个是用户B的钱数增加。假设,用户A赚钱成功了,而数据库执行更新用户B的钱数,出现了不可预知的错误,于是,就出现了这样的尴尬局面,用户A的钱减少了,而用户B的钱未增加,于是,100块钱不翼而飞,这就让人不明觉厉。同时,这违反了数据库事务的一致性的特征。
事务的四大特征
事务主要是针对增、删、改操作,对于查询我们没必要涉及到事务操作。
因而,在这里我们要提到数据的事务四大特征,即原子性、一致性、隔离性、持久性
。如果保证不了这四大特征,我们的数据库就没有意义了。
操作事务的演示
在当前事务添加数据后查询
因而,针对上面的问题,我们需要引用事务,在mysql的命令行中,我们需要开启事务,比如:
-- 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
//插入两条数据
mysql> insert into stu(sname) values('maria');
Query OK, 1 row affected (0.00 sec)
mysql> insert into stu(sname) values('lisa');
Query OK, 1 row affected (0.00 sec)
mysql> select * from stu;
+-----+-------------+
| sno | sname |
+-----+-------------+
| 137 | chenxier17 |
| 138 | chenxier18 |
| 139 | chenxier19 |
| 140 | maria |
| 141 | lisa |
+-----+-------------+
ps:每个transaction只对当前事务有效
我们在插入两条数据后,然后通过select进行查询,它居然插入到表中了,这开启和不开启有什么区别呢?
事务回滚:rollback
如果我们在事务之后,设置rollback,你会发现,如果数据出现了错误,或者,我们没有提交事务,它会回滚,也就是不让事务提交。
这也就保证了数据库的原子性。
mysql> start transaction;
Query OK, 0 rows affected (0.00
mysql> insert into stu(sname) va
Query OK, 1 row affected (0.00 s
mysql> insert into stu(sname) va
Query OK, 1 row affected (0.00 s
mysql> rollback;
Query OK, 0 rows affected (0.00
mysql> select * from stu;
+-----+-------------+
| sno | sname |
+-----+-------------+
| 138 | chenxier18 |
| 139 | chenxier19 |
+-----+-------------+
29 rows in set (0.00 sec)
提交事务:commit
如果,我们添加事务提交,于是,就出现了这样是情况:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into stu(sname) values('maria');
Query OK, 1 row affected (0.00 sec)
mysql> insert into stu(sname) values('lisa');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from stu;
+-----+-------------+
| sno | sname |
+-----+-------------+
| 146 | maria |
| 147 | lisa |
+-----+-------------+
31 rows in set (0.00 sec)
mybatis中的事务
配置数据库的db.properties
我们需要配置configuration.xml中的有关数据库的数据项:
driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://localhost\:3306/student1?useUnicode\=true&characterEncoding\=utf-8&useSSL\=false
user=root
pass=by940202\#
配置configuration.xml
我们配置mysql数据库的事务,采用的jdbc的操作方式,而不是manager操作方式。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<package name="com.mybaits.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${pass}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mybaits/entity/studentmapper.xml" />
</mappers>
</configuration>
配置studentmapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace:命名空间 防止SQL语句的id重名
namespace 命名 包名+类名+mapper文件名
parameterType 指SQL语句的参数类型
resulttype:返回类型
useGeneratedKeys="true" 自增主键
-->
<mapper namespace="com.mybaits.entity.studentmapper">
<insert id="insert_user" parameterType="Student" useGeneratedKeys="true">
insert into stu(sname) values(#{sname});
</insert>
</mapper>
创建SQLSessionFactory类打开一个会话
每个生成的会话对象,都是不同的,也就是生出不同给的实例对象,这样,我们就可确保,每个事务只对当前会话有效,这样,就确保了数据库的一致性。
package com.mybits.util;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBaitsUtill {
private static SqlSessionFactory getsqlSessionFactory() throws IOException {
String resource = "configurationl.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
return sessionFactory;
}
* @return
* @throws IOException
*/
public static SqlSession getSession() throws IOException {
return getsqlSessionFactory().openSession();
}
}
mybatis事务源码
我们怎么保证mybatis事务,是手动提交,还是自动提交呢?我们可别小看了openSession这个方式。如果查看他的源码,可以知道:
package org.apache.ibatis.session;
import java.sql.Connection;
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
}
boolean autoCommit有两个值:
-
true 自动提交
-
false 手动提交
-
不填 手动提交
测试mybatis
创建Student的Javabean文件
package com.mybaits.entity;
public class Student {
public String sname;
public Integer sno;
public Student() {
super();
}
public Student(String sname) {
this.sname = sname;
}
public Student( int sno,String name) {
this.sname = name;
this.sno = sno;
}
public String getName() {
return sname;
}
public void setName(String name) {
this.sname = name;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
}
手动提交事务
public static void main(String[] args) {
try {
SqlSession session2=MyBaitsUtill.getSession();
Student stu2=null;
for(int i=10;i<20;i++){
stu2=new Student("rose"+i); session2.insert("com.mybaits.entity.studentmapper.insert_user",stu2);
stu2=null; //gc回收该对象
}
System.out.println("批处理成功!");
// session2.commit(); 注释
// session2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
输出结果:
log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
批处理成功!
查询数据库的数据:
mysql> select * from stu;
+-----+-------------+
| sno | sname |
+-----+-------------+
| 138 | chenxier18 |
| 139 | chenxier19 |
| 146 | maria |
| 147 | lisa |
+-----+-------------+
31 rows in set (0.00 sec)
虽然控制台输出了批处理成功,但数据库中并有得到插入的数据,这是为什么呢?因为我们没有收到提交事务,如果我们手动提交事务,会得到以下的数据:
-- 如果我们去掉注释,会得到这样的结果:
mysql> select * from stu;
+-----+-------------+
| sno | sname |
+-----+-------------+
| 146 | maria |
| 147 | lisa |
| 168 | rose10 |
| 169 | rose11 |
| 170 | rose12 |
| 171 | rose13 |
| 172 | rose14 |
| 173 | rose15 |
| 174 | rose16 |
| 175 | rose17 |
| 176 | rose18 |
| 177 | rose19 |
+-----+-------------+
41 rows in set (0.00 sec)
数据插入到表中了。
自动提交
如果我们想要自动提交事务,只要把getsqlSessionFactory().openSession(true)
;我们再注释session.commit()
,你会发现,也能将数据插入到数据库中
try {
SqlSession session2=MyBaitsUtill.getSession();
Student stu2=null;
for(int i=20;i<30;i++){
stu2=new Student("rose"+i);
session2.insert("com.mybaits.entity.studentmapper.insert_user",stu2);
stu2=null;
}
System.out.println("批处理成功!");
// session2.commit();
session2.close();
} catch (IOException e) {
e.printStackTrace();
}
| 178 | rose20 |
| 179 | rose21 |
| 180 | rose22 |
| 181 | rose23 |
| 182 | rose24 |
| 183 | rose25 |
| 184 | rose26 |
| 185 | rose27 |
| 186 | rose28 |
| 187 | rose29 |
+-----+-------------+
session1和session2不同步提交事物的影响
如果我们把一个session设置为手动提交,但没有提交事务,把另一个session也设置为手动提交,但提交了事务,这样会不会相互影响呢?答案是不会影响。
我们来做个实验:
try {
SqlSession session1=MyBaitsUtill.getSession();
//我们在187号的学生身后
Student stu1=new Student("张三");
//我们把sno为187的学生名改为李四
Student stu=new Student(187, "李四");
session1.insert("com.mybaits.entity.studentmapper.insert_user",stu1); session1.update("com.mybaits.entity.studentmapper.updataeStudent", stu);
session1.commit();
session1.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
SqlSession session2=MyBaitsUtill.getSession();
Student stu2=null;
for(int i=20;i<30;i++){
stu2=new Student("rose"+i);
session2.insert("com.mybaits.entity.studentmapper.insert_user",stu2);
stu2=null;
}
System.out.println("批处理成功!");
//session2.commit();
session2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
查询结果:
mysql> select * from stu;
+-----+-------------+
| sno | sname |
+-----+-------------+
| 186 | rose28 |
| 187 | 李四 |
| 188 | 张三 |
+-----+-------------+
52 rows in set (0.00 sec)
你会发现,session2虽然没有提交事务,但它并没有影响到session1的事务提交,这是为什么呢?
因为每个事务的都是独立的,当我们通过SqlSession session2=MyBaitsUtill.getSession();
,即开始了一个事务,当我们session1.commit()
即提交了事务,这样可以保证事务的原子性。
session1为什么没有影响到session2呢?
查看session1和session2的引用地址
既然都是通过MyBaitsUtill.getSession();
来创建session对象,session1为什么没有影响到session2呢?我们来看看session1和session2的地址:
System.out.println("session1 stack address:\t"+session1);
System.out.println("session2 stack address:\t"+session2);
输出结果为:
session1 stack address: org.apache.ibatis.session.defaults.DefaultSqlSession@1747c
session2 stack address: org.apache.ibatis.session.defaults.DefaultSqlSession@8bc3b1a
你看,他们的地址都不一样,怎么可能会是同一个session呢,既然不是同一个session,怎么会相互影响呢?
查看session1和session2对象是否相同
我们来看看这两个对象是否相等:
System.out.println("session1 is equals session2:\t"+(session1==session2));
控制台信息:
session1 is equals session2: false
也就是说这session1不等于session2,他们是什么不相同呢?因为他们是栈空间的引用对象的变量,也就是,他们存储的是指向引用对象的首地址,而对象是放在堆里面的,他们所指向的对象的首地址不同,当然不是就不相等了?
有人会问,你怎么知道他们存储的是引用对象的对象的首地址呢?我们还可以做个实验:
SqlSession session3 = null;
SqlSession session4 = null;
System.out.println("session3 stack address:\t" + session3);
System.out.println("session4 stack address:\t" + session4);
当我们将它们输出到控制台时:
session3 story heap address: null
session4 story heap address: null
如果我们为session创建对象:
session3 = MyBaitsUtill.getSession();
session4 = MyBaitsUtill.getSession();
System.out.println("session3 story heap address:\t" + session3);
System.out.println("session4 story heap address:\t" + session4);
session3 story heap address: org.apache.ibatis.session.defaults.DefaultSqlSession@1dfd1301
session4 story heap address: org.apache.ibatis.session.defaults.DefaultSqlSession@51eab608
也就是说这个时候,我们还没有创建SQLSession对象时,堆空间中还没有为Session对象分配地址空间,堆中还没有指向session对象的地址空间。因而,他们存储的地址都为null。当我们在堆中创建对象时,会发现它们这时候地址不为null了,而是由了各自的引用对象的地址了。
ps:当我们在堆中创建对象时,每个对象的在堆中的地址,都是有堆随机分配的,不信的话,我们再执行以上的代码,你会发现,session1和session2的地址改变了:
执行一次:
session3 story heap address: org.apache.ibatis.session.defaults.DefaultSqlSession@1dfd1301
session4 story heap address: org.apache.ibatis.session.defaults.DefaultSqlSession@51eab608
执行两次:
session3 story heap address: org.apache.ibatis.session.defaults.DefaultSqlSession@239f480c
session4 story heap address: org.apache.ibatis.session.defaults.DefaultSqlSession@2e331e19
但是,问题又来了,为什么说是指向对象在堆中的首地址呢?说明这一点时,我们还需要结合数组来看。
引用对象的首地址
我们常说栈里面存放的是指向引用对象的首地址,那么这里的首地址是什么呢?
根据百度知道给出的解释为:在java中,引用对象的首地址是它在堆中存放的起始地址,它后面的地址是用来存放它所包含的各个属性的地址,所以内存中会用多个内存块来存放对象的各个参数,而通过这个首地址就可以找到该对象,进而可以找到该对象的各个属性。
也许,这样的说话,让人理解有点难,我们来举个例子:
public class StudentDao {
/**
*
*我定义一个内部类,用来说明引用对象的首地址
*
*/
static class SClass {
private Map<Integer, List<Student>> maps;
private String className;
public SClass(Map<Integer, List<Student>> maps, String className) {
super();
this.maps = maps;
this.className = className;
}
public Map<Integer, List<Student>> getMaps() {
return maps;
}
public void setMaps(Map<Integer, List<Student>> maps) {
this.maps = maps;
}
public String getClassName() {
return className;
}
public SClass() {
}
public void setClassName(String className) {
this.className = className;
}
}
public static void main(String[] args) {
/**
*我们在栈里面定义一个变量,指向将要实例化的对象
*但目前还没有实例化对象
*/
SClass cla = null;
/**
*
*接下来,我们要实例化一系列的对象,这些对象全部动态分配在堆空间,他们的空间地址是不相同的。
*而我们通过在栈空间随机分配的对象名,来指向这些对象的地址,也就是他们初始化对象的地址,当然,这是一个整体,不包括属性的地址。
*
*/
Student stu1 = new Student(1, "jack");
Student stu2 = new Student(2, "mary");
Student stu3 = new Student(3, "jane");
List<Student> list1 = new ArrayList<Student>();
list1.add(stu1);
list1.add(stu2);
list1.add(stu3);
Student stu4 = new Student(4, "jack");
Student stu5 = new Student(5, "rose");
Student stu6 = new Student(6, "peter");
List<Student> list2 = new ArrayList<Student>();
list2.add(stu4);
list2.add(stu5);
list2.add(stu6);
Map<Integer, List<Student>> map = new HashMap<Integer, List<Student>>();
map.put(1, list1);
map.put(2, list2);
/**
*
*我们在这里实例化对象,与此同时,堆空间会为该对象随机分配一个地址,来存储对象的信息,包括属性,但不包括方法,但常量池不在这里,而是在该类空间中的常量池中,该常量池不是在编译期间能够确认常量的大小和类型的常量池。
*
*为什么不包括方法?因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
*同时,cla指向该对象(整体,不包括属性和方法),就相当于存储 该对象在栈空间的地址
*
*同时在这里,会在栈里随机分配一个局部变量,首先它会在栈里面遍历是否这个map,神奇的是发现有这个map,于是maps指向这map,而map又指向map所引用的对象的在堆中的首地址,于是,maps就相当于指向该首地址,于是乎,就会有这个现象:this.maps=map,cla的属性maps就指向这个地址
因而,它的属性maps并不是首地址,而 new SClass(map, "班级")本身是,而cla存储的是引用该对象的首地址
*/
cla = new SClass(map, "班级");
Map<Integer, List<Student>> clas =cla.getMaps();
Set<Integer> keySets = clas.keySet();
Iterator<Integer> itKeys = keySets.iterator();
while (itKeys.hasNext()) {
Integer key = itKeys.next();
List<Student> stus = clas.get(key);
System.out.println(key+"班的学生:");
for (Student stu : stus) {
System.out.println("对象“"+stu.getName()+"”学生在栈空间存储该对象在堆中引用的首地址为:"+stu);
}
System.out.println("-----------------------");
}
}
}
输出结果为:
1班的学生:
对象“jack”学生在栈空间存储该对象在堆中引用的首地址为:com.mybaits.entity.Student@689d6d87
对象“mary”学生在栈空间存储该对象在堆中引用的首地址为:com.mybaits.entity.Student@3781efb9
对象“jane”学生在栈空间存储该对象在堆中引用的首地址为:com.mybaits.entity.Student@33a17727
----------------------------------------------------
2班的学生:
对象“jack”学生在栈空间存储该对象在堆中引用的首地址为:com.mybaits.entity.Student@2d95bbec
对象“rose”学生在栈空间存储该对象在堆中引用的首地址为:com.mybaits.entity.Student@41649a55
对象“peter”学生在栈空间存储该对象在堆中引用的首地址为:com.mybaits.entity.Student@33d063fd
----------------------------------------------------
说了这么多,就说明了什么是首地址。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99292.html