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

当使用Spring Hibernate CrudRepository Dao模式时,如何获得表上的insert锁?

笪涛
2023-03-14
    null
    @Query(value = "LOCK TABLES mail WRITE", nativeQuery = true)
    void lockTableForWriting();

    @Query(value = "UNLOCK TABLES", nativeQuery = true)
    void unlockTable();

但它们在lockunlock上抛出了一个SQLGrammerException。

我无法获得行锁,因为行还不存在。或者,如果它确实存在,我不会更新任何东西,我只会不插入任何东西,而转移到其他东西。

还有其他情况,我希望用相同的事务id保存多个记录,所以我不能仅仅使列唯一并try/catch保存。

@Repository
public interface MailDao extends CrudRepository<Mail, Long> {

    default Mail safeSave(Mail mail) {
        return Optional.ofNullable(findByTransactionId(mail.getTransactionId()))
                       .orElseGet(() -> save(mail));
    }

    default Mail findByTransactionId(String transactionId) {
        final List<Mail> mails = findAllByTransactionId(transactionId);
        // Snipped code that selects a single entry to return
        // If one can't be found, null is returned.
    }

    @Query(value = "SELECT m " +
                   "  FROM Mail m " +
                   " WHERE m.transactionId = :transactionId ")
    List<Mail> findAllByTransactionId(@Param("transactionId") String transactionId);
}

下面是mail模型的一些内容:

@Entity
@Table(name = "mail")
public class Mail implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "mail_id", unique = true, nullable = false)
    private Long mailId;

    @Column(name = "transaction_id", nullable = false)
    private String transactionId;

    // Snipped code for other parameters, 
    // constructors, getters and setters.
}

下面是调用safeSave的服务方法的一般思想。

@Service
public class MailServiceImpl implements MailService {
    @Inject private final MailDao mailDao;
    // Snipped injected other stuff

    @Override
    public void saveMailInfo(final String transactionId) {
        Objects.requireNonNull(transactionId, "null transactionId passed to saveMailInfo");
        if (mailDao.findByTransactionId(transactionId) != null) {
            return;
        }
        // Use one of the injected things to do some lookup stuff
        // using external services
        if (mailDao.findByTransactionId(transactionId) != null) {
            return;
        }
        // Use another one of the injected things to do more lookup
        if (/* we've got everything we need */) {
            final Mail mail = new Mail();
            mail.setTransactionId(transactionId);
            // Snipped code to set the rest of the stuff
            mailDao.safeSave(mail);
        }
    }
}

我要防止的是对saveMailinfo的两个几乎同时的调用导致数据库中的重复记录。

public interface MailDaoCustom {
    Mail safeSave(Mail mail);
}
public interface MailDao extends CrudRepository<Mail, Long>, MailDaoCustom

则IMPL:

public class MailDaoImpl implements MailDaoCustom {

    @Autowired private MailDao mailDao;
    @Autowired private EntityManager em;

    public Mail safeSave(Mail mail) {
        Query lockTable = em.createNativeQuery("LOCK TABLES mail WRITE");
        Query unlockTable = em.createNativeQuery("UNLOCK TABLES");
        try {
            lockTable.executeUpdate();
            return Optional.ofNullable(mailDao.findByTransactionId(mail.getTransactionId()))
                           .orElseGet(() -> mailDao.save(mail));
        } finally {
            unlockTable.executeUpdate();
        }
    }
}

这是我在测试上面的时候得到的错误。这与我尝试在dao中使用查询来锁定和解锁方法时的情况相同:

SQL Error: 42000, SQLState: 42000
Syntax error in SQL statement "LOCK[*] TABLES MAIL WRITE "; SQL statement:
LOCK TABLES mail WRITE [42000-168]

不过,我的测试是使用单元测试完成的,单元测试使用的是H2内存数据库,而不是MySQL。不过,看起来H2可能并没有真正的表锁定。

public interface MailDaoCustom {
    Mail safeSave(Mail mail);
}
public interface MailDao extends CrudRepository<Mail, Long>, MailDaoCustom
public class MailDaoImpl implements MailDaoCustom {

    @Autowired private MailDao dao;
    @Autowired private EntityManager em;

    public Mail safeSave(Mail mail) {
        // Store a new mail record only if one doesn't already exist.
        Query uniqueInsert = em.createNativeQuery(
                        "INSERT INTO mail " +
                        "       (transaction_id, ...) " +
                        "SELECT :transactionId, ... " +
                        " WHERE NOT EXISTS (SELECT 1 FROM mail " +
                        "                   WHERE transaction_id = :transactionId) ");
        uniqueInsert.setParameter("transactionId", mail.getTransactionId());
        // Snipped setting of the rest of the parameters in the query
        uniqueInsert.executeUpdate();

        // Now go get the record
        Mail entry = dao.findByTransactionId(mail.getTransactionId());
        // Detatch the entry so that we can attach the provided mail object later.
        em.detach(entry);

        // Copy all the data from the db entry into the one provided to this method
        mail.setMailId(entry.getMailId());
        mail.setTransactionId(entry.getTransactionId());
        // Snipped setting of the rest of the parameters in the provided mail object

        // Attach the provided object to the entity manager just like the save() method would.
        em.merge(mail);

        return mail;
    }
}

它并不像我希望的那样干净,我担心我可能犯了一些错误,一些隐藏的东西,我不会发现,直到事情爆炸。但我们拭目以待。

共有1个答案

龙星渊
2023-03-14

我使用了insert INTO...WHERE NOT exists查询和自定义回购。上面在更新2下列出了它,但我也把它放在这里,这样就更容易找到了。

public interface MailDaoCustom {
    Mail safeSave(Mail mail);
}

更新了maildao以实现它:

public interface MailDao extends CrudRepository<Mail, Long>, MailDaoCustom

然后impl如下所示:

public class MailDaoImpl implements MailDaoCustom {

    @Autowired private MailDao dao;
    @Autowired private EntityManager em;

    public Mail safeSave(Mail mail) {
        // Store a new mail record only if one doesn't already exist.
        Query uniqueInsert = em.createNativeQuery(
                        "INSERT INTO mail " +
                        "       (transaction_id, ...) " +
                        "SELECT :transactionId, ... " +
                        " WHERE NOT EXISTS (SELECT 1 FROM mail " +
                        "                   WHERE transaction_id = :transactionId) ");
        uniqueInsert.setParameter("transactionId", mail.getTransactionId());
        // Snipped setting of the rest of the parameters in the query
        uniqueInsert.executeUpdate();

        // Now go get the record
        Mail entry = dao.findByTransactionId(mail.getTransactionId());
        // Detach the entry so that we can attach the provided mail object later.
        em.detach(entry);

        // Copy all the data from the db entry into the one provided to this method
        mail.setMailId(entry.getMailId());
        mail.setTransactionId(entry.getTransactionId());
        // Snipped setting of the rest of the parameters in the provided mail object

        // Attach the provided object to the entity manager just like the save() method would.
        em.merge(mail);

        return mail;
    }
}
 类似资料:
  • 如何获得整个Postgres表的锁,这样就没有其他进程可以更新表中的任何行(但仍然可以使用SELECT读取行)?我认为我想要的锁类型是EXCLUSIVE,但我不确定如何使用ecto查询为整个表获取这样的锁。 谢谢

  • 我已经安装了DropWizard服务,以便在应用程序启动时自动进行任何液化迁移。 当我第一次启动我的Dropwizard服务时,我运行一个以提供信息。此调用还将创建和表,如果它们不存在作为副作用。 一旦在新的Oracle ATP DB上,它在调用listLocks期间创建锁表时抛出以下错误: 服务将抛出该异常,然后pod将重新启动并重复。我手动将该行添加到它试图插入的锁表中,然后下一次启动成功。

  • 我们正在使用Flink表API在Flink应用程序中使用一个Kafka主题。 当我们第一次提交应用程序时,我们首先从我们的自定义注册表中读取最新的模式。然后使用Avro模式创建一个Kafka数据流和表。我的数据序列化器的实现与汇合模式注册表的工作方式类似,方法是检查模式ID,然后使用注册表。因此我们可以在运行时应用正确的模式。

  • 我在一个带有服务器端渲染的react应用程序中使用react router dom,我正在寻找一种获得类似于的东西的方法,但除了路径名之外,我找不到任何东西。是否有任何方法可以使用react-router-dom获取原点?还是其他方式?

  • 我有正则表达式来匹配以下模式, 链接到用例:https://regex101.com/r/wnp1k4/1 如何通过修改正则表达式获得相同的匹配?请帮助。 如果'X或x'在数字之前,订单号不应该被检测到。所以这工作正常。 X123456789 X123456789 x123-456-789 X123-456-789 123-456-789 需要修改正则表达式模式以获得如下所示的医嘱号列表的匹配项…

  • 问题内容: 我想获得可用无线网络的列表。理想情况下,这将通过一些C调用进行,但是我不介意是否必须通过系统调用来混淆它。如果所需的C调用或程序不需要某些奇特的第三方包,甚至会更好。 互联网似乎建议我使用从命令行确实可以解决问题的技巧,但是我宁愿不需要root权限。我只想了解基础知识,什么也不想改变。 问题答案: 该无线工具包-它的iwlist是其中的一部分- 还包含一个无线工具帮助程序库。您需要包含