springboot学习9——整合mongodb

文档数据库——MongoDB

背景:
Redis是一个每秒能够执行10万次以上操作的NoSQL。这个速度远超数据库,可以极大地提高互联网系统的性能,但是它有一些致命的缺陷,其中最为严重的就是计算功能十分有限,例如,在一个10万数据量的List中,我需要满足特定条件的元素在集合或者列表中,我们只有先把元素取出,才能通过条件筛选一个个得到想要的数据,这显然存在比较大的问题。
虽然可以通过Lua脚本去完善,但这样开发者的工作量就大大地增加了。

因此,对于那些需要缓存而且经常需要统计、分析和查询的数据,使用redis就不是那么方便,为解决这个问题,引入本章的主题MongoDB
对于那些需要统计、按条件查询和分析的数据,它提供了支持,它可以说是一个最接近于关系数据库的NoSQL

介绍:
MongoDB:由C++语言编写的一种NoSQL,是一个基于分布式文件存储的开源数据库系统。在负载高时可以添加更多的节点,以保证服务器性能,目的是为Web应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key-value)对组成。这里的MongoDB文档类似于JSON数据集,所以很容易转化成为Java POJO对象或者JavaScript对象,这些字段值还可以包含其他文档、数组及文档数组。

例如,我们完全可以存储以下这个JSON:
代码清单8-1 MongoDB文档示例
{
" id": 1,
“note”: “张三是个好同志”,
“user_name”: “张三”,
“roles”: [
{id : 1, role_name : “高级工程师”},
{id : 2, role_name : "“高级项目经理”}
]
}
这个文档很接近JSON数据集,取出这个文档就可以直接映射为POJO,使用上很方便。与Redis一样,Spring Boot的配置文件也提供了许多关于MongoDB的配置,以方便我们的配置。

首先引入Spring Boot关于MongoDB的starter,然后引入阿里巴巴开发的fastjson的开发包(因为文章会大量使用到JSON的操作)

Maven引入spring-boot-starter-data-mongodb

    org.springframework.boot
    spring-boot-starter-data-mongodb


    com.alibaba
    fastjson
    1.2.39

一、配置MongoDB

引入了关于spring-boot-starter-data-mongodb的依赖,就意味着Spring Boot已经提供了关于MongoDB的配置,也有了默认的可配置项。
Spring Boot关于MongoDB的默认配置

# MONGODB (MongoProperties)
spring.data.mongodb.authentication-database=     # 用于签名的MongoDB数据库
spring.data.mongodb.database=test                # 数据库名称
spring.data.mongodb.field-naming-strategy=       # 使用字段名策略
spring.data.mongodb.grid-fs-database=            # GridFs(网格文件)数据库名称
spring.data.mongodb.host=localhost               # MongoDB服务器,不能设置为URI
spring.data.mongodb.password=                    # MongoDB服务器用户密码,不能设置为URI
spring.data.mongodb.port=                        # MongoDB服务器端口,不能设置为URI
spring.data.mongodb.repositories.type=auto       # 是否启用MongoDB关于JPA规范的编程
spring.data.mongodb.uri=mongodb://localhost/test # MongoDB默认 URI
spring.data.mongodb.username=                    # MongoDB服务器用户名,不能设置为URI

因为有了默认的配置,在默认配置机器不存在MongoDB服务器时就会出现报错,因此往往需要加入自己的配置。

本章的开发配置

spring.data.mongodb.host=192.168.11.131
spring.data.mongodb.username=spring
spring.data.mongodb.password=123456
spring.data.mongodb.port=27017
spring.data.mongodb.database=springboot

配置完成后,Spring Boot会为我们创建关于MongoDB的Spring Bean。Spring Boot会自动创建下列MongoDB的Bean,如MongoTemplate、CustomConversions。

二、使用MongoTemplate实例

Spring Data MongoDB主要是通过MongoTemplate来操作数据,Spring Boot会根据配置自动生成这个对象。
以一个例子来了解下mongoDB的使用:

1.创建一个用户POJO:


// 标识为MongoDB文档
@Document
@Data
public class User implements Serializable {
    private static final long serialVersionUID = -7895435231819517614L;

    // MongoDB文档编号,主键
    @Id
    private Long id;

    // 在MongoDB中使用user_name保存属性
    @Field("user_name")
    private String userName = null;

    private String note = null;

    @Field("create_time")
    private String createTime = null;

    // 角色列表
    private List roles = null;
}

首先这个文档被标识为@Document,说明它将作为MongoDB的文档存在。
注解@id则将对应的字段设置为主键,注解为mongodb包下的。
注解@Field将属性userName就与MongoDB中的user_name属性对应起来(数据库的规范采用下划线分隔,而Java一般采用驼峰式命名),注解为mongodb包下的。

这里还有一个角色列表(属性roles),如你只是想保存其引用,可以使用@DBRef标注,则它只会保存引用信息,而不是具体的角色信息。这里引入了角色列表,我们再来看角色类的定义。

2.角色POJO

@Document
@Data
public class Role implements Serializable {
    private static final long serialVersionUID = -6843667995895038741L;
    private Long id;
    @Field("role_name")
    private String roleName = null;
    private String note = null;

}

这里的@Document标明可以把角色POJO当作一个MongoDB的文档单独使用。如果只是在User中使用角色,没有别的场景使用了,也可以不使用@Document标明对象为MongoDB的文档,
@Field依旧做字段之间命名规则的转换。

3.用户服务接口——UserService:使用MongoTemplate操作文档
将演示使用MongoTemplate如何操作MongoDB的文档。应该说MongoTemplate的操作内容繁多,只是展示那些最为常用的方法,包括增删查改和分页等较为常用的功能。

public interface MongodbUserService {
     void saveUser(User user);

     DeleteResult deleteUser(Long id);

     List findUser(String userName, String note, int skip, int limit);

     UpdateResult updateUser(Long id, String userName, String note);

     User getUser(Long id);
}

4.用户服务实现类及其查询方法

@Service
public class MongodbUserServiceImpl implements MongodbUserService{

    // 注入MongoTemplate对象
    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public User getUser(Long id) {
        return mongoTemplate.findById(id, User.class);
        // 如果只需要获取第一个,也可以采用如下查询方法
        // Criteria  criteriaId  = Criteria.where("id").is(id);
        // Query queryId = Query.query(criteriaId);
        // return mongoTemplate.findOne(queryId, User.class);
    }

    @Override
    public List findUser(
            String userName, String note, int skip, int limit) {
        // 将用户名称和备注设置为模糊查询准则
        Criteria criteria  = Criteria.where("userName").regex(userName)
                .and("note").regex(note);
        // 构建查询条件,并设置分页跳过前skip个,至多返回limit个
        Query query = Query.query(criteria).limit(limit).skip(skip);
        //springboot2.1版本以上 Sort已经不能用 new 实例化了,要通过by
        //query.with(Sort.by(Sort.Direction.ASC,"createTime"));
        // 执行
        List userList = mongoTemplate.find(query, User.class);
        return userList;
    }

    @Override
    public void saveUser(User user) {
        // 使用名称为user文档保存用户信息
        mongoTemplate.save(user, "user");
        // 如果文档采用类名首字符小写,则可以这样保存
        // mongoTemplate.save(user);
    }

    @Override
    public DeleteResult deleteUser(Long id) {
        // 构建id相等的条件
        Criteria  criteriaId  = Criteria.where("id").is(id);
        // 查询对象
        Query queryId = Query.query(criteriaId);
        // 删除用户
        DeleteResult result = mongoTemplate.remove(queryId, User.class);
        return result;
    }

    @Override
    public UpdateResult updateUser(Long id, String userName, String note) {
        // 确定要更新的对象
        Criteria criteriaId  = Criteria.where("id").is(id);
        Query query = Query.query(criteriaId);
        // 定义更新对象,后续可变化的字符串代表排除在外的属性
        Update update = Update.update("userName", userName);
        update.set("note", note);
        // 更新第一个文档
        UpdateResult result = mongoTemplate.updateFirst(query, update, User.class);
        // 更新多个对象
        // UpdateResult result = mongoTemplate.updateMulti(query, update, User.class);
        return result;
    }
}

这个类标注了@Service,所以在定义好扫描包后,Spring会把它自动装配进来。这里的MongoTemplate并不需要自己创建,只要在配置文件中配置好MongoDB的内容,Spring Boot就会自动创建它,使用@Autowired将其注入服务类中。

4.1 getUser方法中,直接调用了getById方法查询结果,如果你并非使用主键进行查询,那么可以参考注释的代码部分,这里使用了准则(Criteria)构建查询条件了,这里的
Criteria criteriaId = Criteria.where(“id”).is(id);
表示构建一个用户主键为变量id的查询准则,然后通过Query query = Query.query(criteriaId);构建查询条件,有了它们就通过findOne查询出唯一的用户信息了。

4.2 saveUser方法中,
如果MongoDB存在id相同的对象,那么就更新其属性;如果是已经存在对象,则它只是对对象进行更新。

4.3 findUser方法,构建了一个查询准则:Criteria criteria = Criteria.where(“userName”).regex(userName).and(“note”).regex(note);
这里的where方法的参数设置为"userName",这个字符串代表的是类User的属性userName;regex方法代表正则式匹配,即执行模糊查询;and方法代表连接字,代表同时满足。
然后通过Query query = Query.query(criteria).limit(limit).skip(skip);构建查询条件,这里的limit代表限制至多返回limit条记录,而skip则代表跳过多少条记录。

最后使用find方法,将结果查询为一个列表,返回给调用者。

4.4 deleteUser方法:
使用主键构建了一个准则,然后采用remove方法将数据删除,执行删除后会返回一个DeleteResult对象来记录此次操作的结果。

4.5 updateUser方法:更新文档操作

通过构建Query对象确认更新什么内容。这里是通过主键确认对应的文档。
然后再定义一个更新对象(Update),在创建它的时候,使用构造方法设置了对用户名的更新,然后使用set方法设置了备注的更新,这样就表明我们只是对这两个属性进行更新,其他属性并不更新,这相当于在MongoDB中使用了“$set”设置。
构造好了Query对象和Update对象后,就可以使用MongoTemplate执行更新了。
它又有updateFirst方法和updateMulti方法,其中updateFirst方法代表只更新第一个文档,而updateMulti方法则是多个满足Query对象限定的文档。
执行更新方法后,会返回一个UpdateResult对象,它有3个属性,分别是matchedCount、modifiedCount和upsertedId:
matchedCount代表与Query对象匹配的文档数,
modifiedCount代表被更新的文档数,
upsertedId表示如果存在因为更新而插入文档的情况会返回插入文档的信息。

5.用户控制器

@Controller
@RequestMapping("/mongodbUser")
public class MongodbUserController {
    // 后面会给出其操作的方法
    @Autowired
    private MongodbUserService userService = null;

    /**
     * 保存(新增或者更新)用户
     * @param user -- 用户
     * @return 用户信息
     */
    @RequestMapping("/save")
    @ResponseBody
    public User saveUser(@RequestBody User user) {
        userService.saveUser(user);
        return user;
    }

    /***
     * 获取用户
     * @param id -- 用户主键
     * @return 用户信息
     */
    @RequestMapping("/get")
    @ResponseBody
    public User getUser(Long id) {
        User user = userService.getUser(id);
        return user;
    }

    /**
     * 查询用户
     * @param userName --用户名称
     * @param note -- 备注
     * @param skip -- 跳过用户个数
     * @param limit -- 限制返回用户个数
     * @return
     */
    @RequestMapping("/find")
    @ResponseBody
    public List addUser(String userName, String note, Integer skip, Integer limit) {
        List userList = userService.findUser(userName, note, skip, limit);
        return userList;
    }

    /**
     * 更新用户部分属性
     * @param id -- 用户编号
     * @param userName -- 用户名称
     * @param note -- 备注
     * @return 更新结果
     */
    @RequestMapping("/update")
    @ResponseBody
    public UpdateResult updateUser(Long id, String userName, String note) {
        return userService.updateUser(id, userName, note);
    }

    /**
     * 删除用户
     * @param id -- 用户主键
     * @return 删除结果
     */
    @RequestMapping("/delete")
    @ResponseBody
    public DeleteResult deleteUser(Long id) {
        return userService.deleteUser(id);


    }
}

6.启动Spring Boot应用程序后

@SpringBootApplication(scanBasePackages = "com.springboot.chapter2.mongodb7")
public class Mongodb7Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Mongodb7Application.class, args);
    }
}

测试get方法:
http://localhost:8080/user/get?id=1

测试findUser方法:
http://localhost:8080/user/find?userName=user¬e=note&skip=5&limit=5

测试update方法:
http://localhost:8080/user/update?id=2&userName=‘bb’¬e=‘3444’

测试delete方法:
http://localhost:8080/user/delete?id=3

三、使用JPA

MongoDB是一个十分接近于关系数据库的NoSQL数据库,它还允许我们使用JPA编程,只是与关系数据库不一样的是提供给我们的接口不是JpaRepository,而是MongoRepository,用一个例子来说明。
使用JPA只需自定义其接口,按照其名称就能够进行扩展,而无须实现接口的方法。

1.MongoDB的JPA接口


// 标识为DAO层
@Repository
// 扩展MongoRepository接口
public interface MongodbUserRepository extends MongoRepository {
    /**
     * 符合JPA规范命名方法,则不需要再实现该方法也可用
     * 意在对满足条件的文档按照用户名称进行模糊查询
     * @param userName -- 用户名称
     * @return 满足条件的用户信息
     */
    List findByUserNameLike(String userName);
}

接口首先要使用@Repository进行标识,表示这是一个DAO层的接口,而接口扩展了MongoRepository接口,它指定了两个类型,一个是实体类型,这个实体类型要求标注@Document,另一个是其主键的类型,这个类型要求标注@Id,这里指定为User类和Long,
对于findByUserNameLike方法而言,它是一个符合JPA命名方式的接口方法,意思为对用户名称进行模糊查询。
一旦定义的接口对MongoRepository进行了扩展,那么你将自动获得MongoRepository中定义的方法,方法如下。
springboot学习9——整合mongodb_第1张图片
接下来将这个接口转变为一个Spring Bean。Spring Data Mongo提供了一个注解——@EnableMongoRepositories,通过它便可以指定扫描对应的接口。

2.@EnableMongoRepositories的使用

@SpringBootApplication(scanBasePackages = "com.springboot.chapter2.mongodb7")
// 指定扫描的包,用于扫描继承了MongoRepository的接口
@EnableMongoRepositories(basePackages="com.springboot.chapter2.mongodb7")
public class Mongodb7Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Mongodb7Application.class, args);
    }
}

在代码中定义了@EnableMongoRepositories注解,并且通过basePackages配置项指定了JPA接口所在的包,这样Spring就能够将UserRepository接口扫描为对应的Spring Bean装配到IoC容器中。

3.修改UserController测试JPA接口

@Controller
@RequestMapping("/mongodbUser")
public class MongodbUserController {

    // 注入接口
    @Autowired
    private MongodbUserRepository userRepository = null;

    // 执行查询
    @RequestMapping("/byName")
    @ResponseBody
    public List findByUserName(String userName) {
        return userRepository.findByUserNameLike(userName);
    }

4.启动Spring Boot的应用,然后在浏览器地址栏输入http://localhost:8080/user/byName?userName=1。

使用自定义查询
JPA的规范虽然可以自动生成查询的逻辑,但是严格来说存在很多的瑕疵。例如,你的查询需要10个字段,或者需要进行较为复杂的查询,显然简陋的JPA规范并不能满足这样的要求。
这时就需要使用自定义查询了。在Spring中,还提供了简单的@Query注解给我们进行自定义查询。例如,如果需要按编号(ID)和用户名称(userName)进行查询。

使用自定义方法

/**
 * 根据编号或者用户名查找用户
 * @param id -- 编号
 * @param userName -- 用户名
 * @return 用户信息
 */
User findUserByIdOrUserName(Long id, String userName);

接下来我们需要一个具体的方法实现这个接口所定义的findUserByIdOrUserName方法,只是这里的UserRepository接口扩展了MongoRepository,如果实现这个接口就要实现其定义的诸多方法,会给使用者带来很大的麻烦,
而JPA为我们自动生成方法逻辑的形式就荡然无存了。这个时候Spring给予了我们新的约定,在Spring中只要定义一个“接口名称+Impl”的类并且提供与接口定义相同的方法,Spring就会自动找到这个类对应的方法作为JPA接口定义的实现。

实现自定义方法

// 定义为数据访问层
@Repository
// 注意这里类名称,默认要求是接口名称(UserRepository) + "impl"
// 这里Spring JPA会自动找到这个类作为接口方法实现
public class UserRepositoryImpl {
    @Autowired// 注入MongoTemplate
    private MongoTemplate mongoTmpl = null;

    // 注意方法名称与接口定义也需要保持一致
    public User findUserByIdOrUserName(Long id, String userName) {
        // 构造id查询准则
        Criteria criteriaId = Criteria.where("id").is(id);
        // 构造用户名查询准则
        Criteria criteriaUserName = Criteria.where("userName").is(userName);
        Criteria criteria = new Criteria();
        // 使用$or操作符关联两个条件,形成或关系
        criteria.orOperator(criteriaId, criteriaUserName);
        Query query = Query.query(criteria);
        // 执行查询返回结果
        return mongoTmpl.findOne(query, User.class);
    }
}

这里并没有实现UserRepository接口,Spring JPA之所以能够找到这个类的findUserByIdOrUserName方法,是因为类的名称是“UserRepository”+“Impl”,而方法名称也是相同的,
只是Spring JPA给予我们默认的约定,只要按照这个约定它就能够找到对应的实现类和方法。如果不喜欢“Impl”这样的后缀,也可以修改默认的配置,如:


@EnableMongoRepositories(
        // 扫描包
        basePackages = "com.springboot.chapter2.mongodb7",
        // 使用自定义后缀,其默认值为Impl 
        // 此时需要修改类名:UserRepositoryImpl-->UserRepositoryStuff
        repositoryImplementationPostfix = "Stuff"
)

这里重新配置注解@EnableMongoRepositories的repositoryImplementationPostfix属性,这样在定义后缀时就需要Stuff而不是Impl了。

你可能感兴趣的:(《深入浅出spring,boot,2.x》,spring,mongodb)