我正在尝试在CrudRepository岛中编写一个默认方法:
我已经研究过在该方法上使用@Lock(LockModeType.PESSIMISTIC_WRITE)
,但是由于它没有关联的@Query
注释,因此我认为它没有任何作用。
我还尝试在dao中创建锁定和解锁方法:
@Query(value = "LOCK TABLES direct_mail WRITE", nativeQuery = true)
void lockTableForWriting();
@Query(value = "UNLOCK TABLES", nativeQuery = true)
void unlockTable();
但是那些人在LOCK
和UNLOCK
上抛出了SQLGrammerException。
我无法获得行锁,因为该行尚不存在。或者,如果确实存在,那么我将不更新任何内容,而只是不插入任何内容并继续进行其他操作。
在其他情况下,我希望使用相同的事务ID保存多条记录,因此我不能只是使该列唯一并尝试/捕获该保存。
在我的服务层中,我尝试进行一些查找并在可能的情况下进行短路,但是仍然有可能多个接近同时的调用可能导致两次尝试插入相同的数据。
由于有多个正在运行的服务实例,因此需要在数据库级别进行处理。因此,两个相互竞争的呼叫可能在与同一数据库通信的不同机器上。
这是我的存储库:
@Repository
public interface MailDao extends CrudRepository<Mail, Long> {
default Mail safeSave(Mail mail) {
return Optional.ofNullable(findByTransactionId(mail.getTransactionId()))
.orElseGet(() -> save(mail));
}
default DirectMail 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
的两个几乎同时的调用导致数据库中的记录重复。
基础数据库是MySQL。
答案 0 :(得分:0)
我进行了一个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;
}
}