使用NoSQL数据库

时间:2021-09-23 01:22:58

一、使用MongoDB持久化文档数据    

    有一些数据的最佳表现形式是文档,也就是说,不要把这些数据分散到多个表、节点或实体中,将这些信息收集到一个非规范化(也就是文档)的结构中更有意义。如果数据之间有明显的关联关系,文档数据库就不太适合了。

我们在一个购物订单系统中学习MongoDB。接下来我们要配置Spring Data MongoDB

package orders.config;

import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@Configuration
@EnableMongoRepositories(basePackages = "orders.db")
public class MongoConfig extends AbstractMongoConfiguration {

@Override
public Mongo mongo() throws Exception {
return new MongoClient();
}

@Override
protected String getDatabaseName() {
return "OrdersDB";
}
}

通过@EnableMongoRepository注解启用Spring Data 的自动化MongoDB repository生成功能,我们让配置类扩展AbstractMongoConfiguration并重载mongo()方法,getDatabaseName()方法,mongo()方法会直接返回一个MongoClient实例而不是声明MongoFactory bean 和MongoTemplate bean。使用MongoClient更加简单。这里需要一个运行在本地的MongoDB 服务器,MongoClient监听的默认的端口27017,如果需要更改端口在MongoClient构造方法中指定端口即可。

下面是Order类

package orders;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.util.Collection;
import java.util.LinkedHashSet;

@Document
public class Order {

@Id
private String id;

@Field("customer")
private String customer;

private String type;

private Collection<Item> items = new LinkedHashSet<>();

public String getId() {
return id;
}

public String getCustomer() {
return customer;
}

public void setCustomer(String customer) {
this.customer = customer;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public Collection<Item> getItems() {
return items;
}

public void setItems(Collection<Item> items) {
this.items = items;
}
}

我们可以看到Order类使用了@Document注解,这样它就能借助MongoTemplate或自动生成的Repository进行持久化,其id属性上使用了@Id注解用来指定它作为文档的ID,除此之外,customer属性上使用了@Field注解,这样在文档持久化的时候customer属性会映射为customer的域。同时items属性它指的是订单中具体条目的集合,在传统的关系型数据库中,这些条目会在另一个数据表中,通过外键进行引用,items域上可能还会有使用JPA的@OneToMany注解。

下面是Item类,Item类本身没有任何注解。

package orders;
public class Item {

private Long id;

private Order order;

private String product;

private double price;

private int quantity;

public Long getId() {
return id;
}

public Order getOrder() {
return order;
}

public String getProduct() {
return product;
}

public double getPrice() {
return price;
}

public int getQuantity() {
return quantity;
}

public void setProduct(String product) {
this.product = product;
}

public void setPrice(double price) {
this.price = price;
}

public void setQuantity(int quantity) {
this.quantity = quantity;
}
}

我们定义OrderRepository扩展MongoRepository,它和Spring Data JPA一样,扩展了Repository的接口将会在运行时自动生成实现。

package orders.db;

import orders.Order;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;

import java.util.List;

public interface OrderRepository extends MongoRepository<Order,String> {
List<Order> findByCustomer(String customer);

List<Order> findByCustomerLike(String customer);

List<Order> findByCustomerAndType(String customer,String type);

List<Order> getByType(String type);

@Query("{customer:‘Chuck Wagon‘}")
List<Order> findChucksOrders();
}

下面是测试类
package orders;

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.List;

import orders.config.MongoConfig;
import orders.db.OrderRepository;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration(classes=MongoConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MongoDbTest {

@Autowired
private OrderRepository orderRepository;

@Autowired MongoOperations mongoOps;

@Before
public void cleanup() {
// Deleting all orders (just in case something is left over from a previous failed run)
orderRepository.deleteAll();
}

@Test
public void testMongoRepository() {
assertEquals(0, orderRepository.count());
Order order = createAnOrder();

// Saving an order
Order savedOrder = orderRepository.save(order);
assertEquals(1, orderRepository.count());

// Finding an order by ID
Order foundOrder = orderRepository.findOne(savedOrder.getId());
assertEquals("Chuck Wagon", foundOrder.getCustomer());
assertEquals(2, foundOrder.getItems().size());

// Finding an order by a single field value
List<Order> chucksOrders = orderRepository.findByCustomer("Chuck Wagon");
assertEquals(1, chucksOrders.size());
assertEquals("Chuck Wagon", chucksOrders.get(0).getCustomer());
assertEquals(2, chucksOrders.get(0).getItems().size());

// Finding an order by a single field value like
List<Order> chuckLikeOrders = orderRepository.findByCustomerLike("Chuck");
assertEquals(1, chuckLikeOrders.size());
assertEquals("Chuck Wagon", chuckLikeOrders.get(0).getCustomer());
assertEquals(2, chuckLikeOrders.get(0).getItems().size());

// Finding an order by multiple field values
List<Order> chucksWebOrders = orderRepository.findByCustomerAndType("Chuck Wagon", "WEB");
assertEquals(1, chucksWebOrders.size());
assertEquals("Chuck Wagon", chucksWebOrders.get(0).getCustomer());
assertEquals(2, chucksWebOrders.get(0).getItems().size());

List<Order> chucksPhoneOrders = orderRepository.findByCustomerAndType("Chuck Wagon", "PHONE");
assertEquals(0, chucksPhoneOrders.size());

// Finding an order by a custom query method
List<Order> chucksOrders2 = orderRepository.findChucksOrders();
assertEquals(1, chucksOrders2.size());
assertEquals("Chuck Wagon", chucksOrders2.get(0).getCustomer());
assertEquals(2, chucksOrders2.get(0).getItems().size());


// Deleting an order
orderRepository.delete(savedOrder.getId());
assertEquals(0, orderRepository.count());

}

private Order createAnOrder() {
Order order = new Order();
order.setCustomer("Chuck Wagon");
order.setType("WEB");
Item item1 = new Item();
item1.setProduct("Spring in Action");
item1.setQuantity(2);
item1.setPrice(29.99);
Item item2 = new Item();
item2.setProduct("Module Java");
item2.setQuantity(31);
item2.setPrice(29.95);
order.setItems(Arrays.asList(item1, item2));
return order;
}

}


二、使用Neo4j操作位图数据

    文档型数据库会将数据存储到粗粒度的文档中,图数据库会将数据存储到多个细粒度的节点中,这些节点之间通过关系建立关联。Spring Data Neo4j提供了将Java对象映射到节点和关联关系的注解、面向模板的Neo4j访问方式以及Repository‘实现的自动化生成功能。

配置Spring Data Neo4j的关键在于声明GraphDatabaseService bean 和启用Neo4j Repository自动生成功能。

package orders.config;

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;

@Configuration
@EnableNeo4jRepositories(basePackages = "orders.db")
public class Neo4jConfig extends Neo4jConfiguration {

public Neo4jConfig() {
setBasePackage("orders");
}

@Bean(destroyMethod="shutdown")
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory()
.newEmbeddedDatabase("/tmp/graphdb");
}
}

@EnableNeo4jRepositories注解能够让Spring Data Neo4j自动生成 Neo4j的Repository实现。定义GraphDatabaseService bean使用GraphDatabaseFactory 来创建嵌入式的Neo4j数据库。

Neo4j定义了两种类型的实体:节点和关联关系。节点反映了应用中的事物,而关联关系定义了这些事物是如何联系在一起的。

Order、Item是两个节点他们之间的关系是Order has items 

下面我们为Order添加注解,使其成为图数据库中的一个节点。

package orders;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelatedTo;

@NodeEntity
public class Order {

@GraphId
private Long id;

private String customer;

private String type;

@RelatedTo(type="HAS_ITEMS")
private Set<Item> items = new LinkedHashSet<Item>();

public String getCustomer() {
return customer;
}

public void setCustomer(String customer) {
this.customer = customer;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public Collection<Item> getItems() {
return items;
}

public void setItems(Set<Item> items) {
this.items = items;
}

public Long getId() {
return id;
}

}
@NodeEntity注解代表这是一个节点,@GraphId注解代表这是一个图Id,@RelatedTo(type="HAS_ITEMS")注解代表Order与一个Item的Set存在关系。

Item也是图数据库中的节点
package orders;

import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;

@NodeEntity
public class Item {

@GraphId
private Long id;

private Order order;

private String product;

private double price;

private int quantity;

public Order getOrder() {
return order;
}

public String getProduct() {
return product;
}

public void setProduct(String product) {
this.product = product;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public int getQuantity() {
return quantity;
}

public void setQuantity(int quantity) {
this.quantity = quantity;
}

public Long getId() {
return id;
}

}
同样我们需要创建自动化的Neo4j Repository
package orders.db;

import java.util.List;

import orders.Order;

import org.springframework.data.neo4j.repository.GraphRepository;

public interface OrderRepository extends GraphRepository<Order> {

List<Order> findByCustomer(String customer);

List<Order> findByCustomerLike(String customer);

List<Order> findByCustomerAndType(String customer, String type);

List<Order> getByType(String type);

// @Query("{customer:‘Chuck Wagon‘}")
// List<Order> findChucksOrders();

}

下面是测试类
package orders;

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import orders.config.Neo4jConfig;
import orders.db.OrderRepository;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration(classes=Neo4jConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Neo4jTest {

@Autowired
private OrderRepository orderRepository;

@Before
public void cleanup() {
// Deleting all orders (just in case something is left over from a previous failed run)
orderRepository.deleteAll();
}

@Test
public void testNeo4jRepository() {
assertEquals(0, orderRepository.count());
Order order = createAnOrder();

// Saving an order
Order savedOrder = orderRepository.save(order);
assertEquals(1, orderRepository.count());

// Finding an order by ID
Order foundOrder = orderRepository.findOne(savedOrder.getId());
assertEquals("Chuck Wagon", foundOrder.getCustomer());
assertEquals(2, foundOrder.getItems().size());

// Finding an order by a single field value
List<Order> chucksOrders = orderRepository.findByCustomer("Chuck Wagon");
assertEquals(1, chucksOrders.size());
assertEquals("Chuck Wagon", chucksOrders.get(0).getCustomer());
assertEquals(2, chucksOrders.get(0).getItems().size());

// Finding an order by a single field value like
List<Order> chuckLikeOrders = orderRepository.findByCustomerLike("Chuck.*");
assertEquals(1, chuckLikeOrders.size());
assertEquals("Chuck Wagon", chuckLikeOrders.get(0).getCustomer());
assertEquals(2, chuckLikeOrders.get(0).getItems().size());

// Finding an order by multiple field values
List<Order> chucksWebOrders = orderRepository.findByCustomerAndType("Chuck Wagon", "WEB");
assertEquals(1, chucksWebOrders.size());
assertEquals("Chuck Wagon", chucksWebOrders.get(0).getCustomer());
assertEquals(2, chucksWebOrders.get(0).getItems().size());

List<Order> chucksPhoneOrders = orderRepository.findByCustomerAndType("Chuck Wagon", "PHONE");
assertEquals(0, chucksPhoneOrders.size());

// Finding an order by a custom query method
// List<Order> chucksOrders2 = orderRepository.findChucksOrders();
// assertEquals(1, chucksOrders2.size());
// assertEquals("Chuck Wagon", chucksOrders2.get(0).getCustomer());
// assertEquals(2, chucksOrders2.get(0).getItems().size());


// Deleting an order
orderRepository.delete(savedOrder.getId());
assertEquals(0, orderRepository.count());

}

private Order createAnOrder() {
Order order = new Order();
order.setCustomer("Chuck Wagon");
order.setType("WEB");
Item item1 = new Item();
item1.setProduct("Spring in Action");
item1.setQuantity(2);
item1.setPrice(29.99);
Item item2 = new Item();
item2.setProduct("Module Java");
item2.setQuantity(31);
item2.setPrice(29.95);
order.setItems(new HashSet<Item>(Arrays.asList(item1, item2)));
return order;
}

}

三、使用Redis操作键值对数据

Redis是一种特殊类型的数据库,它被称之为key-value存储,key-value存储与哈希Map有很大的相似性,可以把它理解为持久化的哈希Map。
连接Redis
package cart;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisConfig {

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

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

}
我们配置RedisConnectionFactory bean 通过默认构造器创建的连接工厂会向localhost上的6379端口创建连接。顾名思义,Redis连接工厂会生成到Redis key-value存储的连接,我们使用RedisTemplate简化数据访问,能够让我们持久化各种类型的key和value。
将RedisConnectionFactory bean 注入到 RedisTemlate中,RedisTemplate使用两个类型进行参数化,第一个是key的类型,第二个是value的类型。

下面是Product类,实现了Serializable接口,因为Redis存储对象时需要将其序列化,取出时需要反序列化。
package cart;

import java.io.Serializable;

public class Product implements Serializable {

private static final long serialVersionUID = 1L;

private String sku;
private String name;
private float price;

public String getSku() {
return sku;
}

public void setSku(String sku) {
this.sku = sku;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public float getPrice() {
return price;
}

public void setPrice(float price) {
this.price = price;
}

}

下面是测试类的一部分
package cart;

import static org.junit.Assert.*;

import java.util.List;
import java.util.Set;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=RedisConfig.class)
public class CartTest {

/*
* IMPORTANT: This test class requires that a Redis server be running on
* localhost and listening on port 6379 (the default port).
*/

@Autowired
private RedisConnectionFactory cf;

@Autowired
private RedisTemplate<String, Product> redis;

@After
public void cleanUp() {
redis.delete("9781617291203");
redis.delete("cart");
redis.delete("cart1");
redis.delete("cart2");
}

@Test
public void workingWithSimpleValues() {
Product product = new Product();
product.setSku("9781617291203");
product.setName("Spring in Action");
product.setPrice(39.99f);

redis.opsForValue().set(product.getSku(), product);

Product found = redis.opsForValue().get(product.getSku());
assertEquals(product.getSku(), found.getSku());
assertEquals(product.getName(), found.getName());
assertEquals(product.getPrice(), found.getPrice(), 0.005);
}
}