事务操作暨mybatis的事务操作

什么是事务?

百度百科的解释:

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

维基百科的解释:

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

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

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

比如我们在进行转账事务时,如果用户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,你会发现,如果数据出现了错误,或者,我们没有提交事务,它会回滚,也就是不让事务提交。这也就保证了数据库的原子性。

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)

如果,我们添加事务提交,于是,就出现了这样是情况:

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

ps: 我们配置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类打开一个会话

PS:每个生成的会话对象,都是不同的,也就是生出不同给的实例对象,这样,我们就可确保,每个事务只对当前会话有效,这样,就确保了数据库的一致性。

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事务,是手动提交,还是自动提交呢?因而,我们可别小看了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      |
+-----+-------------+

影响

如果我们把一个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呢?

既然都是通过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,怎么会相互影响呢?我们来看看这两个对象是否相等:

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
----------------------------------------------------

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

猜你喜欢

转载自blog.csdn.net/lvoelife/article/details/78767769