刚刚接触SSH框架,虽然可能这个框架已经比较过时了,但是个人认为,SSH作为一个成熟的框架,作为框架的入门还是可以的。
马马虎虎学完了Hibernate的基础,总结一点心得之类的。
学习Hibernate的乐观锁时:
- 首先要知道为什么要用乐观锁。之所以要用乐观锁,就是为了避免脏数据。这很像数据库原理中的共享锁(读锁)和排它锁(写锁)。不管是乐观锁、共享锁、排它锁,其目的都是为了保证数据的一致性,也可以说是保证数据的正确性。所有锁的本质都是一样的。
- 其次要知道什么时候要用乐观锁。一般有多个事务要对同一数据进行操作时,就需要使用乐观锁。比如很经典的银行取钱问题,只有用锁来保证数据只能被一个事务所使用,在该事务结束使用之前,别的事务都不能对它做任何事,否则就可能出现丢失修改、读脏数据、数据不一致等问题。
- 第三要知道为什么可以使用乐观锁。Hibernate本身是对JDBC的轻量级封装,其目的是为了使开发人员可以像操作对象一样操作数据库,其本质就是数据库操作,所以数据库的锁机制是可以实现和使用的。
- 第四要知道乐观锁的实现机制。version元素是利用一个递增的整数来跟踪数据表中记录的版本的。在读取数据时,会将version一同读取出来,而在更新时,将version+1(使用hql在update时不校验version)。将提交数据的version与数据库库表中对应记录的version进行比较,如果提交的数据的version大于数据库库表中记录的version,则执行更新,否则便认为是过期或无效数据,不执行更新,并抛出异常。
- 最后要知道怎么使用乐观锁。
- 先要有一个entity class。在该类里面增加一个version属性,设为int类型,这个字段表示版本信息。
【注:代码中有@的可以不用管,这个是注解方式。即直接在类上使用注解,来达到相同的配置效果。】
1 package hibernate; 2 3 import java.util.Set; 4 5 import javax.persistence.Column; 6 import javax.persistence.Entity; 7 import javax.persistence.GeneratedValue; 8 import javax.persistence.GenerationType; 9 import javax.persistence.Id; 10 import javax.persistence.Table; 11 12 @Entity 13 @Table(name = "product_") 14 public class Product { 15 int id; 16 String name; 17 float price; 18 Category category; 19 Set<User> users; 20 int version; 21 22 @Column(name = "version") 23 public int getVersion() { 24 return version; 25 } 26 public void setVersion(int version) { 27 this.version = version; 28 } 29 30 @Column(name = "users") 31 public Set<User> getUsers() { 32 return users; 33 } 34 public void setUsers(Set<User> users) { 35 this.users = users; 36 } 37 38 @Column(name = "category") 39 public Category getCategory() { 40 return category; 41 } 42 public void setCategory(Category category) { 43 this.category = category; 44 } 45 46 @Id 47 @GeneratedValue(strategy = GenerationType.IDENTITY) 48 @Column(name = "id") 49 public int getId() { 50 return id; 51 } 52 public void setId(int id) { 53 this.id = id; 54 } 55 56 @Column(name = "name") 57 public String getName() { 58 return name; 59 } 60 public void setName(String name) { 61 this.name = name; 62 } 63 64 @Column(name = "price") 65 public float getPrice() { 66 return price; 67 } 68 public void setPrice(float price) { 69 this.price = price; 70 } 71 }
- 接下来就是进行修改entity class的配置文件。在"类名.hbm.xml"文件中,增加一个version字段,用于版本信息控制,这就是乐观锁的核心机制。
【注:version标签必须跟在id标签后面,否则会有错,运行程序会报错,无法读取XML文件。 hibernate需要访问的属性一定要在"类名.hbm.xml"中定义】
1 <?xml version="1.0"?> 2 <!DOCTYPE hibernate-mapping PUBLIC 3 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 4 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 5 6 <hibernate-mapping package="hibernate"> 7 8 <!-- *****表示类Product对应表product_***** --> 9 <class name="Product" table="product_"> 10 11 <!-- *****表示属性id,映射表里的字段id***** --> 12 <id name="id" column="id"> 13 <!-- *****id的自增长方式采用数据库的本地方式***** --> 14 <generator class="native"> 15 </generator> 16 </id> 17 18 <!--version标签必须跟在id标签后面 --> 19 <version name="version" column="ver" type="int"></version> 20 21 <!-- *****只写了属性name,没有通过column="name" 显式的指定字段,那么字段的名字也是name.***** --> 22 <property name="name" /> 23 <property name="price" /> 24 25 <!-- 使用标签many-to-one设置多对一关系 --> 26 <!-- name="category" 对应Product类中的category属性 --> 27 <!-- class="Category" 表示对应Category类 --> 28 <!-- column="cid" 表示指向category_表的外键 --> 29 <many-to-one name="category" class="Category" column="cid"/> 30 31 <set name="users" table="user_product" lazy="false"> 32 <key column="pid"/> 33 <many-to-many column="uid" class="User"/> 34 </set> 35 </class> 36 37 </hibernate-mapping>
- 还有在"包名.cfg.xml"文件中要配置好映射。
<mapping resource="hibernate/Product.hbm.xml" />
- 最后测试一下就好了。
1 public class TestHibernate { 2 3 public static void main(String[] args) { 4 SessionFactory sf = new Configuration().configure().buildSessionFactory(); 5 6 Session s1 = sf.openSession(); 7 Session s2 = sf.openSession(); 8 9 s1.beginTransaction(); 10 s2.beginTransaction(); 11 12 Product p1 = (Product) s1.get(Product.class, 1); 13 System.out.println("产品原本价格是: " + p1.getPrice()); 14 p1.setPrice(p1.getPrice() + 1000); 15 16 Product p2 = (Product) s2.get(Product.class, 1); 17 p2.setPrice(p2.getPrice() + 1000); 18 19 s1.update(p1); 20 s2.update(p2); 21 22 s1.getTransaction().commit(); 23 s2.getTransaction().commit(); 24 25 Product p = (Product) s1.get(Product.class, 1); 26 System.out.println("经过两次价格增加后,价格变为: " + p.getPrice()); 27 28 sf.close(); 29 } 30 31 }
- 运行结果:在main线程中出现了报错,因为s1已经修改了数据,但是s2也想修改,但是version的值已经由1变为2了,所以s2不能执行,报错Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect),意思是"行被另一事务更新或删除(或未保存的值映射不正确)",即s1已经修改了该行,产品原本价格是: 10000.0,程序执行完之后为11000.0,s2没有执行。
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. 八月 05, 2018 10:49:36 上午 com.mchange.v2.log.MLog <clinit> 信息: MLog clients using java 1.4+ standard logging. 八月 05, 2018 10:49:38 上午 com.mchange.v2.c3p0.C3P0Registry banner 信息: Initializing c3p0-0.9.1 [built 16-January-2007 14:46:42; debug? true; trace: 10] 八月 05, 2018 10:49:38 上午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager 信息: Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@7cf67e4e [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@3c314b76 [ acquireIncrement -> 2, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, debugUnreturnedConnectionStackTraces -> false, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 2x00zq9x27t6vc4ficmh|4d1b0d2a, idleConnectionTestPeriod -> 3000, initialPoolSize -> 5, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 50000, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 100, maxStatementsPerConnection -> 0, minPoolSize -> 5, nestedDataSource -> com.mchange.v2.c3p0.DriverManagerDataSource@ce96782d [ description -> null, driverClass -> null, factoryClassLocation -> null, identityToken -> 2x00zq9x27t6vc4ficmh|52feb982, jdbcUrl -> jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8, properties -> {user=******, password=******} ], preferredTestQuery -> null, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false; userOverrides: {} ], dataSourceName -> null, factoryClassLocation -> null, identityToken -> 2x00zq9x27t6vc4ficmh|4fcd19b3, numHelperThreads -> 3 ] Hibernate: select product0_.id as id0_0_, product0_.ver as ver0_0_, product0_.name as name0_0_, product0_.price as price0_0_, product0_.cid as cid0_0_ from product_ product0_ where product0_.id=? Hibernate: select users0_.pid as pid0_1_, users0_.uid as uid1_, user1_.id as id3_0_, user1_.name as name3_0_ from user_product users0_ inner join user_ user1_ on users0_.uid=user1_.id where users0_.pid=? Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=? Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=? Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=? 产品原本价格是: 10000.0 Hibernate: select product0_.id as id0_0_, product0_.ver as ver0_0_, product0_.name as name0_0_, product0_.price as price0_0_, product0_.cid as cid0_0_ from product_ product0_ where product0_.id=? Hibernate: select users0_.pid as pid0_1_, users0_.uid as uid1_, user1_.id as id3_0_, user1_.name as name3_0_ from user_product users0_ inner join user_ user1_ on users0_.uid=user1_.id where users0_.pid=? Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=? Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=? Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=? Hibernate: update product_ set ver=?, name=?, price=?, cid=? where id=? and ver=? Hibernate: update product_ set ver=?, name=?, price=?, cid=? where id=? and ver=? Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [hibernate.Product#1] at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1950) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2594) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2494) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2821) at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133) at hibernate.TestHibernate.main(TestHibernate.java:404)
- PS:除了乐观锁还有悲观锁,弄懂了乐观锁之后可以研究一下悲观锁。