SpringMvc in Action——持久化数据

小时候,我们骑车就能获得快乐。后来随着慢慢长大,我们需要的自行车就能满足了,我们可能想要装载一些生活用品,或者接送孩子,或者要去很远的地方。我们的需求超出了自行车的功能范围。
在持久化的世界里,JDBC就像是自行车。对于分内的工作,它能很好的完成。然而,随着应用越来越复杂。我们可能需要将对象属性映射到数据库的列上,我们可能需要自动生成语句和查询,我们可能需要一些更复杂的特性,譬如:

  • 延迟加载(Lazy loading):比如我们在查询一个对象A的时候,这个对象的属性包括了普通的String/int等等,也包括了其他对象B,我们这时候可能不太需要B,然而B的开销不容小觑,所以延迟加载允许我们在需要的时候才开销。
  • 预先抓取(Eager fetching):这与延迟加载相对。假设我们需要对象A及其关联的对象B,预先抓去功能可以在一个操作中将他们全部从数据库里抓取出来,节省了多次查询的成本。
  • 级联(Cascading):有时候更改数据库会同时修改其他关联的表,当我们删除对象A我们可能也希望删除关联的对象B。

一些可用的框架提供了这样的服务,这些服务的通用名称是ORM(object-relational mapping,对象-关系映射)。在持久层使用ORM工具,可以节省数千行的代码和大量的开发时间。

在Spring中集成Hibernate

我们只关注Spring和Hibernate的集成,而不会涉及太多Hibernate使用时的复杂细节。

声明Hibernate的Session工厂
Hibernate所需的主要接口是org.hibernate.Session。这个Session接口提供了基本的数据访问功能,如更新、保存、删除以及从数据库加载对象的功能。通过Session接口,应用程序的Repository能满足所有的持久化需求。
获取Session对象的标准方式是通过工厂方法:SessionFactory接口的实现类。除了一些其他的任务,SessionFactory主要负责Hibernate Session的打开、关闭以及管理。
从Hibernate3.1版本开始,Spring提供了三个SessionFactory bean:
在这里插入图片描述
这些SessionFactory的选择取决于使用哪个版本的Hibernate以及是使用XML还是使用注解来定义对象-数据库之间的映射关系,如果使用Hibernate3.2或者更高版本(直到Hibernate4.0,但不包括4.0)并且使用XML定义映射,那么,你需要定义Spring的org.springframework.orm.hibernate3包中的LocalSessionFacrotyBean:

package spittr.config;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
public class DataSoueceConfiguration {

    @Bean
    public DataSource Data(){
        BasicDataSource ds=new BasicDataSource();
        ds.setDriverClassName("org.h2.Driver");
        ds.setUrl("jdbc:h2:tcp://localhost:8080/promusician");
        ds.setUsername("root");
        ds.setPassword("root");
        ds.setInitialSize(5);
        ds.setMaxActive(10);
        return ds;
    }

   @Bean
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource){
       LocalSessionFactoryBean sessionFactoryBean=new LocalSessionFactoryBean();
       sessionFactoryBean.setDataSource(dataSource);
       sessionFactoryBean.setMappingResources(new String[]{"Spitter.hbm.xml"});
       Properties properties=new Properties();
       properties.setProperty("dialect","org.hibernate.dialect.H2Dialect");
       sessionFactoryBean.setHibernateProperties(properties);
       return sessionFactoryBean;
   }
}

这里使用dataSource属性装配了一个DataSource bean的引用。mappingResources属性列出了一个或多个Hibernate映射文件,最后hibernateProperties属性配置了Hibernate如何操作的细节。本例中我们配置Hibernate使用H2数据库并且按照H2Dialect来构建SQL。

如果你倾向于使用注解的方式来定义持久化,而且并不使用Hibernate4:

@Bean
    public AnnotationSessionFactoryBean sessionFactory(DataSource dataSource){
       AnnotationSessionFactoryBean sessionFactoryBean=new AnnotationSessionFactoryBean();
       sessionFactoryBean.setDataSource(dataSource);
       sessionFactoryBean.setPackagesToScan(new String[]{"spittr.domain"});
       Properties properties=new Properties();
       properties.setProperty("dialect","org.hibernate.dialect.H2Dialect");
       sessionFactoryBean.setHibernateProperties(properties);
       return sessionFactoryBean;
   }

可以观察出,他只改变了一个方法。就是通过java配置而不是xml配置。packageToScan属性告诉Spring扫描一个或多个包来查找域类,这些类通过注解的方式来表明要使用Hibernate进行持久化,这些类可以使用的注解包括JPA的@Entity或@MappedSuperclasses以及Hibernate的@Entity
或者使用Hibernate4的LocalSessionFactoryBean
它支持两种方式的配置。也就是把上面的类名改改就行。

构建不依赖与Spring的Hibernate代码
使用上下文Session(Contextual Session)会直接将Hibernate SessionFactory装配到Repository中,并使用它来获取Session:


@Repository
public class HibernateSpitterRepository implements SpitterRepository{

    private SessionFactory sessionFactory;

    @Inject
    public HibernateSpitterRepository(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }

    public long count() {
        return findAll().size();
    }

    public Spitter save(Spitter spitter) {
        Serializable id = currentSession().save(spitter);  
        return new Spitter((Long) id,
                spitter.getUsername(),
                spitter.getPassword(),
                spitter.getFullName(),
                spitter.getEmail(),
                spitter.isUpdateByEmail());
    }

    public Spitter findOne(long id) {
        return (Spitter) currentSession().get(Spitter.class, id);
    }
    ...
}

这个程序有几个地方需要注意。首先,我们通过@Inject注解让Spring自动将一个SessionFactory注入到HibernateSpitterRepository的sessionFactory中。接下来的currentSession()方法中,我们使用sessionFactory获得当前事务的Session。
@Repository用于声明Component。
我们使用Session,那异常抓换怎么处理呢?为了添加异常转换功能,我们只需要在Spring的应用上下文中添加一个bean:

@Bean
public BeanPostProcessor persistenceTranslation(){
	return new PersistenceExceptionTranslationPostProcessor();
}

这个bean会在所有拥有@Repository注解的类上添加一个通知其,这样就会捕捉任何平台相关的异常,并且以Spirng非检查型数据访问异常的形式抛出。

使用Redis

Redis是一种特殊类型的数据库,它被称之为key-value存储。顾名思义,key-value存储保存的是键值对。实际上key-value存储与哈希Map有很大的相似性。可以不太夸张的说,它就是持久化的Hash Map。

当然Redis是一种NoSQL,所以这里不需要使用并配置DataSource。

链接到Redis
Redis提供了四种连接工厂:
在这里插入图片描述
具体使用哪种,本书不做讲解。
我们可以将连接工厂配置为Spring中的bean。例如:

	@Bean
	public RedisConnectionFactory redisCF() {
		return new JedisConnectionFactory();
	}

这种默认构造器会想localhost的6379端口创建连接,并且没有密码,如果你有额外需求你可以使用:

@Bean
	public RedisConnectionFactory redisCF() {
		JedisConnectionFactory cf= new JedisConnectionFactory();
		cf.setHostName("redis-server");
		cf.setPort(7379);
		cf.setPassword("foobared");
		return cf;
	}

当然,我们这里的显式配置都很简单,实际工程项目还复杂的多,这里只做最基本的介绍。

使用RedisTemplate

Redis Connection Factory会生成一个RedisConnection。借助这个RedisConnection,可以存储和读取数据。比如,我们可以使用这个RedisConnection存储一个信息:

	//假设已经注入了一个工厂类ConnectionFactory
	RedisConnection conn=rediusConnectitonFactory.getConnection();
	conn.set("greeting".getBytes(),"Hello World".getBytes());//键值对的形式存储。

毫无疑问,这可以正常运行,但是你愿意使用字节数组来存储信息?
当然,Spring Data Redis以模版的形式提供了较高级的数据访问方案。实际上,它提供了两个模版:

  • RedisTemplate
  • StringRedisTemplate

RedisTemplate可以极大简化Redis的数据访问,持久化各种类型的key-value,而不仅仅是字节数组。但在认识到key-value通常是String类型的后,StringRedisTemplate又扩展了RedisTemplate,只关注String类型。

我们这样构建RedisTemplate:

	@Bean
	public RedisTemplate<String, Product> redisTemplate(RedisConnectionFactory cf) {
		RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>();
		redis.setConnectionFactory(cf);
		return redis;
	}

这样构建StringRedisTemplate:

@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory cf){
	return new StringRedisTemplate(cf);
}

有了RedisTemplate之后,我们就可以开始保存获取以及删除键值对了。
在这里插入图片描述
假设我们想通过RedisTemplate<String,Product>保存Product,其中key是num属性的值,如下代码展示了如何借助opsForValue()来完成该功能:

redis.opsForValue().set(product.getNum(),product);//存储num-对象的键值对

如果想获得该对象Product,且num=123456:

Product product=redis.opsForValue().get("123456");

那我们使用List类型的value与之类似,只需要使用opsForList()即可:(假设我们希望在List类型的条目尾部添加一个值)

redis.opsForList().rightPush("cart",product);

使用rightPush()在列的尾部添加了一个Product,所使用这个列表在存储时,key为cart。如果这个key尚未存在,将会创建一个。同理leftPush()会在头部添加。
我们也有方法获取单个元素:

Product first=redis.opsForList().leftPop("cart");
Product last=redis.opsForList().rightPop("cart");

这两个方法有个副作用是获取的同时,也出队列了。我们如果只想获取值的话,甚至可能是在中间获取,那么我们可以使用range()方法:

List<Product> products=redis.opsForList().range("cart",2,12);

除了操作列表外,我们还可以使用opsForSet()操作Set,最为常用的操作就是Set中添加一个元素:

redis.opsForSet().add("cart",product);

在我们有多个Set并填充值之后,我们就可以对这些Set进行一些操作,比如获取其差异,求交集,求并集:

List<Product> diff=redis.opsForSet().difference("cart","cart1");
List<Product> union=redis.opsForSet().union("cart","cart1");
List<Product> isect=redis.opsForSet().isect("cart","cart1");

当然还可以移除它的元素:

redis.opsForSet().remove(product);

使用key和value的序列化器
Redis会将key-value用序列化器进行序列化。Spring Data Redis提供了多种序列化器,我们这里暂时不做介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值