mysql的事务是什么 mybatis框架中的事务配置 mybatis中的自动提交事务和手动提交事务 深入理解mybatis事务源码 通过对象的地址来理解mysbaits中的会话 对象的首地址

什么是事务?

百度百科的解释

事务是指为单个逻辑单元执行一系列的操作,要么完全地执行,要么就不能执行。

维基百科的解释

数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

逻辑单元

这两个解释不太一样,但都提到逻辑单元。

就我们所知道的,在计算机中有很多指令,每一条指令都构成一个执行单元,如果那一条指令出现问题,可能都会造成系统的瘫痪。当然,数据库的事务也不例外。

我们在执行一条插入数据语句,或者更新数据和删除数据语句时,这都代表一个事务,为什么我们在平常的操作的中,没有感觉到呢?因为,在数据库的世界中,如果我们没有开启事务时,数据库就会默认执行这条事务。当然,这在一定程度上,是不大安全的,为什么这么说呢?

比如我们在进行转账事务时,如果用户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
----------------------------------------------------

说了这么多,就说明了什么是首地址。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网全栈开发实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值