数据库范式
数据库有三大范式,各有各的功能:
第一范式
保持属性的原子性,不能再分,同一列中不能有多个值,只能代表一个不可再分的属性。
遇到违背第一范式
找到那个属性,进行拆分,分成两个属性
找到那个属性,保留一个属性,另一个属性与主键建立另外一个表。
第二范式
第二范式 属性完全依赖于主键:就是主键区分每一行数据,其他非主键属性全部依赖它,就是如果主键为一个,只需要主键能够区分就可以。但如果主键有多个,非主属性不能依赖于主键的部分属性,必须依赖于主键的所有属性。就是说对于任何一个非主属性都是最小的主键。
遇到违背第二范式:
找到那个不符合第二范式的非主属性字段,再找到他的部分主键属性字段,移除建立新的表。如果有多个非主属性字段不符合,看他们的部分主键字段是否相同,如果相同,就将他们放在同一个表,要不然就各自一个表。
第三范式
属性不依赖其他的任何非主属性字段。
遇到违背第三范式
找到那个属性和非主属性,然后移除那个属性,保留非主属性字段作为外键,移除的字段和非主属性字段建立一个新表(以非主属性为主键)
事务
事务的四大特性(ACID)
原子性(A):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性(C):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性(I):隔离性是当多个用户并发访问数据库时,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性(D): 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的
事物的隔离级别(默认事务级别为可重复读)
数据库事务基本就两种可能:读取事务(select)和修改事务(update,insert)。
在没有事务隔离控制的情况下,多个事务同一时刻对同一数据的操作可能就会影响到最终期望的结果。
一般有如下问题:
(1) 两个更新事务同时修改一条数据时,很显然这种情况是最严重的了,程序中无论如何也不能出现这种情况,因为它会造成更新的丢失!
(2) 一个更新事务更新一条数据时,另一个读取事务读取了还没提交的更新,这种情况下会出现读取到脏数据。
(3) 一个读取事务读取一条数据时,另一个更新事务修改了这条数据,这时就会出现不可重复的读取。
(4)一个读取事务读取时,另一个插入事务(注意此处时插入)插入了一条新数据,这样就可能多读出一条数据,出现幻读。
前三种是对同一条数据的并发操作,对程序的结果可能产生致命影响。
综上四个情况,我们可以大致这样简单的理解
A) 修改时允许修改(丢失更新)
B) 修改时允许读取(脏读)
C) 读取时允许修改(不可重复读)
D) 读取时允许插入(幻读)
从上到下问题越来越不严重,但所需的性能开销却越大。
了解一下补不考虑隔离性的后果:
脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在读取数据,同时另外一个事务对同一数据进行了修改,并未提交,但数据已经改变,事务就读取了这一数据,但是另外那个事务并未提交,最后发生回滚,读取的数据就出错了。
不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
虚读(幻读)
幻读就是一个事务在读取或者是修改数据,同一时刻另一个事务进行了插入并提交了,发现读取少了一条,修改也忘了这一条。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
MySQL数据库的四种事务隔离级别
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read);
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果;
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。
简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。
MySQL事务在Java中的应用
jdbc事务
当插入多条sql语句时,如果不设置手动提交事务,那么默认每条插入语句都是一个事务,每次都要提交事务(自动提交)。设置手动提交事务的话,可以在循环前开启事务,循环结束后再提交事务,只需要提交一次事务。
jdbc操作数据库:
try {
Connection conn = null;
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("url", "user", "password");
connection.setAutoCommit(false);//设置手动提交事务
// 执行之后不提交事务。对于Select没有影响, 但对于Insert和Update的话, 没有提交数据就不会被修改
// 用法大多数是在要执行多条语句才提交。单条语句没必要手动提交。
PreparedStatement stmt = con.prepareStatement("insert into money(name,money) values (?,?)");
stmt.setString(1, "JACK2");
stmt.setDouble(2, 100.0);
stmt.executeUpdate(); //单条插入语句的执行
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("select * from money");
while (rs.next()) {
System.out.println(rs.getString("momey"));
} //单条查询语句查询
PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (String name : names) { //name一个String类型的数组
ps.setString(1, name);
ps.setBoolean(2, gender);
ps.setInt(3, grade);
ps.setInt(4, score);
ps.addBatch(); // 添加到batch
}
// 执行batch:
int[] ns = ps.executeBatch();//多条插入语句一起执行
ps.clearBatch();//清除之前的数据
connection.commit();//提交
connection.rollback();//回滚
connection.setAutoCommit(true);//再把自动提交打开
}catch(){}
//对数据库操作时需要用try-catch
总结一下:
jdbc它自带自动提交事务,但只是对于每一条语句,如果执行多条语句就会造成效率不高。所以需要自己手动提交,但手动提交必须注意你操作的数据在多条语句中有没有冲突,判断是否会出现上面的那些隔离问题。
spring事务
spring事务包括spring编程式事务管理,还有一个spring声明式事务管理。
编程式事务管理很少用,通过Transaction Template手动管理事务,实际应用中很少使用,这里不讲!!!
用XML配置声明式事务,推荐使用(代码侵入性最小),实际是通过AOP实现。
基于纯注解的事务管理:
纯注解:使用注解注入属性(对象也是)
创建dao和service对象
在service类里面定义dao类型属性 Autowired不需要匹配对象属性的value值userDao。
注入属性第二个注解 @Resource,这个注解的name值需要与dao对象属性的value值userDao匹配。
Spring对数据库的操作在jdbc上面做了深层次的封装,也就是工具类 jdbcTemplate
可以使用jdbcTemplate来封装DataSource来操控数据。
如果使用的是mybatis还是使用sqlSessionFactory来操控数据
spring.xml的配置文件:
<!-- 注解注入 -->
<!-- 所有注解都要注入 -->
<context:component-scan base-package="com.coshaho.*" />
<!-- 引入jdbc配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 使用spring初始化DataSource -->
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 使用JdbcTemplate封装DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- myBatis文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
<property name="mapperLocations" value="classpath:xy/mapping/*.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="xy.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--使用注释事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
配置了注释事务,就可以使用了。
@Transactional
使用这一注解就可以使用失事务了。
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
@Transactional 注解应该只被应用到public 方法上,这是由 Spring AOP 的本质决定的。
@Transactional 只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能. 建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
为了保证原子性,通过需要用事务来控制,要么全部成功,要么全部失败。比如定义在一个方法中,多条sql操作语句要么全部成功,要么全部失败。
这个注解有几个属性: