当前位置: 首页 > 知识库问答 >
问题:

Spring、JPA和Hibernate——如何在没有并发问题的情况下递增计数器

卫兴邦
2023-03-14

我正在玩Spring和JPA/Hibernate,我对在表中增加计数器的正确方法有点困惑。

我的REST API需要根据用户操作增加或减少数据库中的一些值(在下面的例子中,喜欢或不喜欢一个标记将使标记表中的计数器增加或减少1)

tagRepository是一个JpaRepository(Spring数据),我这样配置了事务

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>

@Controller
public class TestController {

    @Autowired
    TagService tagService

    public void increaseTag() {
        tagService.increaseTagcount();
    }
    public void decreaseTag() {
        tagService.decreaseTagcount();

    }
}

@Transactional
@Service
public class TagServiceImpl implements TagService {


    public void decreaseTagcount() {
        Tag tag = tagRepository.findOne(tagId);
        decrement(tag)
    }

    public void increaseTagcount() {
        Tag tag = tagRepository.findOne(tagId);
        increment(tag)
    }

    private void increment(Tag tag) {
        tag.setCount(tag.getCount() + 1); 
        Thread.sleep(20000);
        tagRepository.save(tag);
    }

    private void decrement(Tag tag) {
        tag.setCount(tag.getCount() - 1); 
        tagRepository.save(tag);
    }
}

正如您所看到的,我在之前故意在增量上睡眠了20秒。save()以测试并发场景。

初始标签计数器= 10;

1) 用户调用increaseTag,代码进入Hibernate状态,因此实体的值=11,而数据库中的值仍然是10

2)用户调用decreaseTag并检查所有代码。该值是数据库现在= 9

3)睡眠结束,点击。保存计数为11的实体,然后点击。保存()

当我检查数据库时,该标记的值现在等于11。。而实际上(至少是我想要实现的)它等于10

这种行为正常吗?还是@Transactional注释不做是工作?

共有3个答案

夏英发
2023-03-14

这里值得一提的是spring-boot-mongodb JPA-如何在没有并发问题的情况下增加计数器:

根据官方文件,在MongoDB中:

修改单个文档时,请同时修改db.collection。findAndModify()和update()方法自动更新文档。有关这些方法的交互和操作顺序的更多详细信息,请参阅原子性和事务。

在Mongo shell的情况下,我们可以简单地运行查找和修改,如下所示:

> db.idGenerator.findAndModify({query: {identifier: "identified_by_Id"}, update: {$inc: {counter:1}}  })
{
    "_id" : ObjectId("5e115560ff14992f34fd18c6"),
    "identifier" : "identified_by_Id",
    "counter" : 1
}
> db.idGenerator.find()
{ "_id" : ObjectId("5e115560ff14992f34fd18c6"), "identifier" : "identified_by_Id", "counter" : 2 }
> db.idGenerator.findAndModify({query: {identifier: "identified_by_Id"}, update: {$inc: {counter:1}}  })
{
    "_id" : ObjectId("5e115560ff14992f34fd18c6"),
    "identifier" : "identified_by_Id",
    "counter" : 2
}
>

findAndModify()总是递增/递减并返回之前为计数器存在的实际值。

就JPA而言,首先创建一个Model,然后创建Repository and Service类,以获得唯一的ID,如下所示:

模型类

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

@Document
public class IdGenerator {

  @Id
  private String identifier;

  private long counter;

  public String getIdentifier() {
    return identifier;
  }

  public void setIdentifier(String identifier) {
    this.identifier = identifier;
  }

  public long getCounter() {
    return counter;
  }

  public void setCounter(long counter) {
    this.counter = counter;
  }
}

蒙戈存储库类:

import org.springframework.data.mongodb.repository.MongoRepository;
import sample.data.mongo.models.IdGenerator;


public interface IdGeneratorRepository extends MongoRepository<IdGenerator, String> {

}

// Id生成器服务类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
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 org.springframework.stereotype.Service;
import sample.data.mongo.models.IdGenerator;
import sample.data.mongo.repository.IdGeneratorRepository;

@Service
public class IdGeneratorService {

  @Autowired
  private IdGeneratorRepository idGeneratorRepository;

  @Autowired
  private MongoTemplate mongoTemplate;

  public long generateId(String key) {

    Query query = new Query();
    // key = identified_by_Id;
    Criteria criteria = new Criteria("identifier").is(key);
    query.addCriteria(criteria);

    Update update = new Update();
    update.inc("counter", 1);

    FindAndModifyOptions options = new FindAndModifyOptions();
    options.upsert(true);
    options.returnNew(true);

    IdGenerator idGenerator = mongoTemplate
        .findAndModify(query, update, options, IdGenerator.class);

    return idGenerator.getCounter();
  }
}

通过使用上述方法GenerateId,它将始终为特定键返回一个递增的数字。

欧阳楚
2023-03-14

例如,使用乐观锁定。这应该是解决您的问题的最简单解决方案。有关更多详细信息,请参阅 -

白飞飙
2023-03-14

最简单的解决方案是将并发性委托给数据库,只需依赖当前修改行上的数据库隔离级别锁:

增量就这么简单:

UPDATE Tag t set t.count = t.count + 1 WHERE t.id = :id;

减量查询为:

UPDATE Tag t set t.count = t.count - 1 WHERE t.id = :id;

UPDATE查询会锁定修改的行,从而防止其他事务在当前事务提交之前修改同一行(只要不使用READ_UNCOMMITTED)。

 类似资料:
  • 问题内容: 我正在使用Spring和JPA / Hibernate玩耍,我对在表中增加计数器的正确方法有些困惑。 我的REST API需要根据用户操作来增加和减少数据库中的某些值(在示例中,在下面的示例中,喜欢或不喜欢标签会使计数器在标签表中增加或减少1)。 是(Spring-data),并且我已经像这样配置了交易 如您所见,我故意在增加20秒的睡眠时间之后才能够测试并发方案。 初始标签计数器=

  • 我有一个现有的spring 4项目(mvc、jdbc等),我试图将其移植到spring boot,但我做不到(许多依赖项存在问题,没有人能解释我是如何做到这一点的)。但现在我只想在现有项目中使用Spring数据JPA。这是pom的一个主要依赖项: 我使用EntityManagerFactorybean(在所有示例中都使用)完成了现有配置。数据库配置: 用于测试的Derby数据库。但主要数据库类型是

  • 问题内容: 我正在尝试设置spring xml配置,而不必创建进一步的。但是,即使我将数据库属性包括在 spring.xml: 我在这里想念什么? 问题答案: 在entityManagerFactory bean定义中指定“ packagesToScan”和“ persistenceUnitName”属性。 请注意,这适用于Spring版本> 3.1

  • 问题内容: 我想在Python中实现自定义列表类作为的子类。为了获得所有列表操作的完全类型兼容性,我需要从基类中重写的最少方法集是什么? 这个问题表明至少需要重写。从进一步的研究,也和是必需的。所以我有这段代码: 下列语句即使没有覆盖方法也可以按需工作: 这些语句仅与上述类定义中的覆盖方法一起使用: 我唯一不知道如何实现的是使扩展切片返回正确的类型: 我需要在类定义中添加些什么才能获得类型? 另外

  • 问题内容: 我在整个应用程序中都使用spring 。现在,我也想为一个没有的创建一个。那有可能吗? 问题答案: JPA要求每个实体都有一个ID。因此,不可以,没有ID的实体是不允许的。 每个JPA实体都必须有一个主键。 从JPA规范 您可能想从这里阅读更多有关JPA如何处理数据库端没有ID的情况的信息(请参阅“无主键”)。

  • 我想使用POST动词在带有flask restplus的VM上执行操作,但当没有主体时,它总是导致400。 结果是400{“消息”:“浏览器(或代理)发送了一个此服务器无法理解的请求。” 如果我只是简单地从post转换为get,它就可以正常工作。但是,我真的想使用POST动词,因为这是我需要遵循的标准动词,用于自定义非CRUD操作。我有没有用flask restplus把自己画到角落里? 注意:对