springboot+jpa+mongodb开发实战
1、新建maven工程,引入springboot和mongodb依赖。
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
</dependencies>
2、编写application.yml配置文件,配置mongodb数据库
application.yml
spring:
data:
mongodb:
uri: mongodb://localhost:27017/webserver
3、编写实体类并做映射配置
User.java
package com.xxx.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "xx_user")
@Data
@AllArgsConstructor
@ToString
public class User {
@Id
private Long id;
private String username;
private String password;
private int age;
}
4、编写Repository接口,访问数据库。
UserRepository.java
package com.xxx.dao;
import com.xxx.entity.User;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends MongoRepository<User,Long>{
}
5、编写Service接口,并新建测试。
UserService.java
package com.xxx.service;
import com.xxx.dao.UserRepository;
import com.xxx.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findOne(Long id){
return userRepository.findById(id).get();
}
public User save(User user){
return userRepository.save(user);
}
public List<User> queryAll(){
return userRepository.findAll();
}
}
package com.xxx.service;
import com.xxx.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.junit.Assert.*;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void findOne() throws Exception {
User user = userService.findOne(3L);
System.out.println(user);
assertEquals("null",user.getPassword());
}
@Test
public void save() throws Exception {
User user = new User(3L,"convertor","null",0);
assertEquals("convertor",userService.save(user).getUsername());
}
@Test
public void queryAll() throws Exception {
List<User> list = userService.queryAll();
assertEquals(3,list.size());
}
}
6、补充:如何让mongodb中的collection去掉属性_class
添加bean配置
package com.xxx.config;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@Configuration
public class MongoMappingConfig {
@Bean
public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory,
MongoMappingContext context, BeanFactory beanFactory){
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver,context);
try{
converter.setCustomConversions(beanFactory.getBean(CustomConversions.class));
}catch (Exception e){
e.printStackTrace();
}
//don't save column _class to mongo collection
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
}
测试:
> db.xx_user.find()
{ "_id" : NumberLong(1), "username" : "windows", "password" : "win7", "age" : 7, "_class" : "com
.xxx.entity.User" }
{ "_id" : NumberLong(2), "username" : "mongodb", "password" : "mongo", "age" : 3, "_class" : "co
m.xxx.entity.User" }
{ "_id" : NumberLong(3), "username" : "convertor", "password" : "null", "age" : 0 }
>
前面两条数据是没有配置mongodb的TypeMapper生成的,带有_class属性字段,_id为3的数据是配置了TypeMapper之后生成的,去掉了_class属性。
spring-data-mongodb两种实现方式
spring-data-mongodb其实有两种实现方式,一种是直接继承MongoRepository接口,dao层的实现,默认提供了CRUD的各种方法,几乎不用编写任何代码。另一种是通过MongoTemplate来操作数据库,这样需要自定义dao层接口,默认没有任何接口可以使用,但是MongoTemplate提供了操作数据库的各种CRUD接口,因此,dao层接口需要我们再次封装一次。
两种方式均需要配置mongo:client,mongo:db-factory,mongoTemplate类。
这里只整合spring+mongodb,因此构建的工程相对简单:
maven工程pom.xml配置文件:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
实体定义:
BaseEntity
package com.xxx.springmongo.entity;
import java.util.Date;
import org.springframework.data.annotation.Id;
public class BaseEntity {
@Id
protected String id;
protected Date createDate;
protected Date modifyDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
package com.xxx.springmongo.entity;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection="xx_user")
public class User extends BaseEntity{
private String username;
private String password;
private String mobile;
@DBRef
private Role role;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public void setRole(Role role) {
this.role = role;
}
public Role getRole() {
return role;
}
@Override
public String toString() {
return "{\"id\":"+id+",\"username\":"+username+",\"password\":"+
password+",\"mobile\""+mobile+",\"role\":"+role+"}";
}
}
package com.xxx.springmongo.entity;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection="xx_role")
public class Role extends BaseEntity{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "{\"id\":"+id+",\"name\":"+name+"}";
}
}
实体之间的关系,用户(User),角色(Role)均继承BaseEntity,另外用户有一个角色的引用。通过注解@DBRef来指定他们在数据库中的关系。
配置文件
mongo.properties
mongo.host=127.0.0.1
mongo.port=27017
mongo.db=springmongo
spring-mongo.xml 核心配置(需要引入spring-mongo.xsd)
<context:property-placeholder location="classpath:mongo.properties"/>
<context:component-scan base-package="com.xxx.springmongo">
</context:component-scan>
<mongo:mongo-client id="mongo-client" host="${mongo.host}"
port="${mongo.port}">
<mongo:client-options/>
</mongo:mongo-client>
<mongo:repositories base-package="com.xxx.springmongo.dao"/>
<mongo:db-factory dbname="${mongo.db}" mongo-ref="mongo-client"/>
<bean id="mongoTemplate"
class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>
第一种实现方式:dao层接口继承MongoRepository
UserDao
package com.xxx.springmongo.dao;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import com.xxx.springmongo.entity.Role;
import com.xxx.springmongo.entity.User;
public interface UserDao extends MongoRepository<User, String>{
public User findByRole(Role role);
@Query(value="{'role.$id': ?0 }")
public User findByRoleId(ObjectId id);
}
package com.xxx.springmongo.dao;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.xxx.springmongo.entity.Role;
public interface RoleDao extends MongoRepository<Role, String>{
}
默认情况下,我们只是定义了dao层的用户接口和角色接口,并没有实现相关方法。UserDao中为了实现通过关联角色查询用户,增加了两个接口。
服务层接口:
RoleService
package com.xxx.springmongo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.stereotype.Service;
import com.xxx.springmongo.dao.RoleDao;
import com.xxx.springmongo.entity.Role;
@Service
public class RoleService {
@Autowired
private RoleDao roleDao;
public Role save(Role role) {
return roleDao.save(role);
}
public Role findById(String id) {
return roleDao.findById(id).get();
}
public Role findByName(String name) {
Role role = new Role();
role.setName(name);
ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("name", match->match.contains());
Example<Role> example = Example.of(role, matcher);
return roleDao.findOne(example).get();
}
}
package com.xxx.springmongo.service;
import java.util.List;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xxx.springmongo.dao.UserDao;
import com.xxx.springmongo.entity.Role;
import com.xxx.springmongo.entity.User;
@Service
public class UserService {
@Autowired
private UserDao userDao;
public User save(User user) {
return userDao.save(user);
}
public User findById(String id) {
return userDao.findById(id).get();
}
public List<User> findAll(){
return userDao.findAll();
}
public User findByRole(Role role) {
return userDao.findByRoleId(new ObjectId(role.getId()));
}
}
测试类:
AppTest
package com.xxx.springmongo;
import java.util.Date;
import org.junit.Assert;
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;
import com.xxx.springmongo.entity.Role;
import com.xxx.springmongo.entity.User;
import com.xxx.springmongo.service.RoleService;
import com.xxx.springmongo.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring-mongo.xml")
public class AppTest {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Test
public void save() {
Role role = new Role();
role.setName("superadmin");
role.setCreateDate(new Date());
role.setModifyDate(new Date());
Role save = roleService.save(role);
Assert.assertEquals("superadmin", save.getName());
}
@Test
public void query() {
Role role = roleService.findByName("admin");
System.out.println(role);
//Assert.assertEquals("admin", role.getName());
User user = new User();
user.setCreateDate(new Date());
user.setUsername("admin");
user.setPassword("admin");
user.setMobile("15011186302");
user.setModifyDate(new Date());
user.setRole(role);
User tmp = userService.save(user);
Assert.assertEquals("admin", tmp.getUsername());
}
@Test
public void query1() {
Role role = roleService.findByName("admin");
User tmp = userService.findByRole(role);
System.out.println(tmp);
Assert.assertEquals("admin", tmp.getUsername());
}
}
以上测试用例只写了添加角色,添加用户,按照角色名查找角色,按照角色查找用户。其中按照角色查找用户是一个关联查询,在MongoRepository的实现方式中有两种实现:
第一种是和普通的按照属性查找的方式实现。
第二种是稍微有一点改变,就是模拟在mongo-shell下通过关联属性role字段的$id来查询
> db.xx_user.find({"role.$id":new ObjectId("5bdfbbc2e6c87e4f147f7fab")})
{ "_id" : ObjectId("5bdfed63e6c87e569c9f8865"),
"username" : "admin",
"password" : "admin",
"mobile" : "15011186302",
"role" : DBRef("xx_role",
ObjectId("5bdfbbc2e6c87e4f147f7fab")),
"createDate" : ISODate("2018-11-05T07:12:35.739Z"),
"modifyDate" : ISODate("2018-11-05T07:12:35.739Z"),
"_class" : "com.xxx.springmongo.entity.User" }
>
第二种实现方式:通过MongoTemplate来操作数据库
IBaseDao定义一个基础接口
package com.xxx.springmongo.mongo.dao;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Page;
public interface IBaseDao<T> {
void save(T entity);
void update(T entity);
void delete(Serializable... ids);
T find(Serializable id);
List<T> findAll();
List<T> findAll(String order);
List<T> findByProp(String propName,Object value);
List<T> findByProp(String propName,Object value,String order);
List<T> findByProps(String[] propName,Object[] values);
List<T> findByProps(String[] propName,Object[] values,String order);
T uniqueByProp(String propName,Object value);
T uniqueByProps(String[] propName,Object[] values);
int countByCondition(String[] params,Object[] values);
}
package com.xxx.springmongo.mongo.dao;
import com.xxx.springmongo.entity.User;
public interface UserDao extends IBaseDao<User>{
}
package com.xxx.springmongo.mongo.dao.impl;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import com.xxx.springmongo.mongo.dao.IBaseDao;
public abstract class BaseDaoImpl<T> implements IBaseDao<T> {
protected abstract Class<T> getEntityClass();
@Autowired
protected MongoTemplate mongoTemplate;
@Override
public void save(T entity) {
mongoTemplate.save(entity);
}
@Override
public void update(T entity) {
Map<String, Object> map = null;
try {
map = parseEntity(entity);
}catch (Exception e) {
e.printStackTrace();
}
String id = null;
Object value = null;
Update update = new Update();
if(map!=null && map.size()>0) {
for(String key:map.keySet()) {
if(key.startsWith("{")) {
id = key.substring(key.indexOf("{")+1,key.indexOf("}"));
value = map.get(key);
}else {
update.set(key, map.get(key));
}
}
}
mongoTemplate.updateFirst(new Query().addCriteria(Criteria.where(id).is(value)), update, getEntityClass());
}
@Override
public void delete(Serializable... ids) {
if(ids!=null&&ids.length>0) {
for(Serializable id:ids) {
mongoTemplate.remove(mongoTemplate.findById(id, getEntityClass()));
}
}
}
@Override
public T find(Serializable id) {
return mongoTemplate.findById(id, getEntityClass());
}
@Override
public List<T> findAll() {
return mongoTemplate.findAll(getEntityClass());
}
@SuppressWarnings("deprecation")
@Override
public List<T> findAll(String order) {
List<Order> orderlist = parseOrder(order);
if(orderlist==null||orderlist.size()==0) {
return findAll();
}
return mongoTemplate.find(new Query().with(new Sort(orderlist)), getEntityClass());
}
@Override
public List<T> findByProp(String propName, Object value) {
return findByProp(propName, value, null);
}
@SuppressWarnings("deprecation")
@Override
public List<T> findByProp(String propName, Object value, String order) {
Query query = new Query();
query.addCriteria(Criteria.where(propName).is(value));
List<Order> orderlist = parseOrder(order);
if(orderlist!=null && orderlist.size()>0) {
query.with(new Sort(orderlist));
}
return null;
}
@Override
public List<T> findByProps(String[] propName, Object[] values) {
return findByProps(propName, values, null);
}
@Override
public List<T> findByProps(String[] propName, Object[] values, String order) {
Query query = createQuery(propName, values, order);
return mongoTemplate.find(query, getEntityClass());
}
@Override
public T uniqueByProp(String propName, Object value) {
return mongoTemplate.findOne(new Query().addCriteria(Criteria.where(propName).is(value)),getEntityClass());
}
@Override
public T uniqueByProps(String[] propName, Object[] values) {
Query query = createQuery(propName, values, null);
return mongoTemplate.findOne(query, getEntityClass());
}
@Override
public int countByCondition(String[] params, Object[] values) {
Query query = createQuery(params, values, null);
Long count = mongoTemplate.count(query, getEntityClass());
return count.intValue();
}
protected Map<String, Object> parseEntity(T t) throws Exception{
Map<String, Object> map = new HashMap<>();
String id = "";
Field[] declaredFields = getEntityClass().getDeclaredFields();
for(Field field:declaredFields) {
if(field.isAnnotationPresent(Id.class)) {
field.setAccessible(true);
map.put("{"+field.getName()+"}", field.get(t));
id = field.getName();
break;
}
}
Method[] declaredMethods = getEntityClass().getDeclaredMethods();
if( declaredFields != null&& declaredFields.length > 0 ) {
for(Method method:declaredMethods) {
if(method.getName().startsWith("get")&&method.getModifiers()==Modifier.PUBLIC) {
String fieldName = parse2FieldName(method.getName());
if(!fieldName.equals(id)) {
map.put(fieldName, method.invoke(t));
}
}
}
}
return map;
}
private String parse2FieldName(String method) {
String name = method.replace("get", "");
name = name.substring(0, 1).toLowerCase()+name.substring(1);
return name;
}
@SuppressWarnings("deprecation")
public Query createQuery(String[] propName,Object[] values,String order) {
Query query = new Query();
//where
if(propName!=null&&values!=null) {
for(int i=0;i<propName.length;i++) {
query.addCriteria(Criteria.where(propName[i]).is(values[i]));
}
}
List<Order> orderlist = parseOrder(order);
if(orderlist!=null && orderlist.size()>0) {
query.with(new Sort(orderlist));
}
return query;
}
public List<Order> parseOrder(String order){
List<Order> list = null;
if(order!=null && !"".equals(order)) {
list = new ArrayList<>();
String[] fields = order.split(",");
Order o = null;
String[] items = null;
for(int i=0;i<fields.length;i++) {
if(fields[i]==null) {
continue;
}
items = fields[i].split(" ");
if(items.length==1) {
o = new Order(Direction.ASC,items[0]);
}else if(items.length==2) {
o = new Order("desc".equalsIgnoreCase(items[1])?Direction.DESC:Direction.ASC, items[0]);
}else {
throw new RuntimeException("order field parse error");
}
list.add(o);
}
}
return list;
}
}
package com.xxx.springmongo.mongo.dao.impl;
import org.springframework.stereotype.Repository;
import com.xxx.springmongo.entity.User;
import com.xxx.springmongo.mongo.dao.UserDao;
@Repository
public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao{
@Override
protected Class<User> getEntityClass() {
return User.class;
}
}
package com.xxx.springmongo.mongo.service;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xxx.springmongo.entity.Role;
import com.xxx.springmongo.entity.User;
import com.xxx.springmongo.mongo.dao.UserDao;
@Service
public class UserServiceImpl {
@Autowired
private UserDao userDao;
public void save(User user) {
userDao.save(user);
}
public void update(User user) {
userDao.update(user);
}
public User findById(String id) {
return userDao.find(id);
}
public User findByRole(Role role) {
//return userDao.uniqueByProp("role", role);
return userDao.uniqueByProp("role.$id", new ObjectId(role.getId()));
}
}
package com.xxx.springmongo;
import org.junit.Assert;
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;
import com.xxx.springmongo.entity.Role;
import com.xxx.springmongo.entity.User;
import com.xxx.springmongo.mongo.service.UserServiceImpl;
import com.xxx.springmongo.service.RoleService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring-mongo.xml")
public class MongoServiceTest {
@Autowired
private UserServiceImpl userService;
@Autowired
private RoleService roleService;
@Test
public void query() {
Role role = roleService.findByName("admin");
User user = userService.findByRole(role);
System.out.println(user);
Assert.assertEquals("admin", user.getUsername());
}
}
通过操作MongoTemplate来操作数据库,我们为了有一层dao,额外添加了一个公共的接口IBaseDao,然后用一个抽象的类BaseDaoImpl来实现了所有定义的接口。当我们定义具体的UserDaoImpl时,只需要继承BaseDaoImpl和实现自己的UserDao接口。这里面实现关联查询也有两种方法:
和查询普通属性一样按照属性role来查询user。
通过给role.$id赋值new ObjectId(id)的方式来查询。
以上给出了两种spring-data-mongodb整合dao层的实现方式。他们两个的区别在于继承MongoRepository接口时,无需实现接口,如果有特别需要的接口,比如按照某一个属性查找时,只需要在dao接口中声明即可。例如:
public User findByRole(Role role);
复杂的查询可以通过@Query来指定具体的参数,例如:
@Query(value="{‘role.$id’: ?0 }")
public User findByRoleId(ObjectId id);
这种实现方式,接口无需注解@Repository,但是需要在配置文件中加入一个配置项:<mongo:repositories base-package="com.xxx.springmongo.dao"/>
,该配置项指定dao层接口所在的包。
dao层通过MongoTemplate来操作数据库的方式,需要我们自己定义需要的相关接口,然后实现,可以通过Query来指定条件,实现复杂查询。该种实现方式需要在实现接口类上注解@Repository,另外,无需配置配置文件中加入配置<mongo:repository base-package="com.xxx.springmongo.mongo.dao"/>
来指定需要扫描的dao层包。关联查询时,也有两种实现方式:直接按照属性查找和给role.$id赋值。
public User findByRole(Role role) {
//return userDao.uniqueByProp("role", role);
return userDao.uniqueByProp("role.$id", new ObjectId(role.getId()));
}