| 知乎专栏 |
更新/删除操作需要加上 @Modifying 注解
@Modifying
@Query("update Money m set m.isDeleted=?2 where m.money=?1")
void updateStateByMoney(Long money, Byte state);
@Modifying(clearAutomatically=true, flushAutomatically = true)
@Modifying
@Query(value = "INSERT INTO notification_status(consumer_id,notification_id) VALUES( :consumerId, :notificationId)", nativeQuery = true)
void read(@Param("consumerId") int consumerId, @Param("notificationId") int notificationId);
package api.repository.oracle;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import api.domain.oracle.Member;
@Repository
public interface MemberRepository extends CrudRepository<Member, Long> {
public Page<Member> findAll(Pageable pageable);
// public Member findByBillno(String billno);
public Member findById(String id);
@Query("SELECT m FROM Member m WHERE m.status = 'Y' AND m.id = :id")
public Member findFinishById(@Param("id") String id);
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PersonRepository extends JpaRepository<Person, Long> {
@Query("SELECT p FROM Person p WHERE LOWER(p.lastName) = LOWER(:lastName)")
public List<Person> find(@Param("lastName") String lastName);
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?0", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
insert ignore
@Modifying
@Query(value = "insert ignore into emp(create, modified, user_id, user_name, user_nickname, user_mail) values(?1, ?2, ?3, ?4, ?5, ?6)", nativeQuery = true)
void insertIgnoreEmployee(Timestamp create, Timestamp modified, String userId, String name, String nickname, String mail);
这里的nativeQuery=true代表在执行这个方法的时候使用原生sql语句,直接写数据库中的实际表名和表中的字段名,而不是实体表名。
@Modifying
@Query(nativeQuery = true, value = "UPDATE project p, (SELECT MIN(start) AS start, MAX(finish) AS finish FROM project WHERE parent_id = :id) t SET p.start = t.start, p.finish = t.finish WHERE p.id = :id")
public void updateStartAndFinishById(@Param("id") Long id);
在什么情况下使用呢?例如上面,同时操作两张表,做更新,如果不使用 nativeQuery = true 无法实现。
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#_native_queries
@Query(value = "SELECT u FROM User u ORDER BY id") Page<User> findAllUsersWithPagination(Pageable pageable);
package api.domain;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
@Entity
@Table(indexes = { @Index(name = "address", columnList = "from_address,to_address"), @Index(name = "contractAddress", columnList = "contractAddress") })
public class TransactionHistory implements Serializable {
private static final long serialVersionUID = 6710992220657056861L;
@Id
@Column(name = "blockNumber", unique = true, nullable = false, insertable = true, updatable = false)
private int blockNumber;
private String timeStamp;
private String hash;
@Column(name = "from_address")
private String from;
@Column(name = "to_address")
private String to;
private String value;
private String gas;
private String gasPrice;
private String isError;
private String contractAddress;
private String gasUsed;
private String symbol;
public TransactionHistory() {
// TODO Auto-generated constructor stub
}
public int getBlockNumber() {
return blockNumber;
}
public void setBlockNumber(int blockNumber) {
this.blockNumber = blockNumber;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getGas() {
return gas;
}
public void setGas(String gas) {
this.gas = gas;
}
public String getGasPrice() {
return gasPrice;
}
public void setGasPrice(String gasPrice) {
this.gasPrice = gasPrice;
}
public String getIsError() {
return isError;
}
public void setIsError(String isError) {
this.isError = isError;
}
public String getContractAddress() {
return contractAddress;
}
public void setContractAddress(String contractAddress) {
this.contractAddress = contractAddress;
}
public String getGasUsed() {
return gasUsed;
}
public void setGasUsed(String gasUsed) {
this.gasUsed = gasUsed;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return "TransactionHistory [blockNumber=" + blockNumber + ", timeStamp=" + timeStamp + ", hash=" + hash + ", from=" + from + ", to=" + to + ", value=" + value + ", gas=" + gas + ", gasPrice=" + gasPrice + ", isError=" + isError + ", contractAddress=" + contractAddress + ", gasUsed=" + gasUsed + ", symbol=" + symbol + "]";
}
}
package api.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import api.domain.TransactionHistory;
@Repository
public interface TransactionHistoryRepository extends CrudRepository<TransactionHistory, Integer> {
@Query(value = "SELECT * FROM transaction_history th WHERE (th.from_address = :address or th.to_address = :address) and contract_address is NULL",
countQuery = "SELEÇT count(*) FROM transaction_history th WHERE (th.from_address = :address or th.to_address = :address) and contract_address is NULL",
nativeQuery = true)
public Page<TransactionHistory> findEthByAddress(@Param("address") String address, Pageable pageable);
}
通过实体返回数据有时结果集非常庞大,可能会影响性能,这时我们只需要返回指定字段即可。
@Query(value = "select u.userName, ui.name, ui.gender, ui.description from UserInfo ui, User u where u.id = ui.userId") public List<Object> getCustomField();
@Query(value = "select new map(u.userName, ui.name, ui.gender, ui.description) from UserInfo ui, User u where u.id = ui.userId") public List<Map<String, Object>> getCustomField();
临时写一个新的模型
public class MyModel implements Serializable {
private String userName;
private String name;
private String gender;
private String description;
public MyModel() {};
public MyModel(String userName, String name, String gender, String description) {
this.userName = userName;
this.name = name;
this.gender = gender;
this.description = description;
}
}
使用构造方法赋值
@Query(value = "select new cn.netkiller.model.MyModel(u.userName, ui.name, ui.gender, ui.description) from UserInfo ui, User u where u.id = ui.userId") public List<MyModel> getAllRecord();
@GetMapping("discovery/group")
// @Deprecated(since = "v4.0.0", forRemoval = true)
public AigcResponse group(Pageable pageable) {
Page<DiscoveryPictureGroup> iterable = pictureGroupService.discoveryPictureGroup(pageable);
return new AigcResponse(iterable);
}
package cn.netkiller.service;
import cn.netkiller.repository.PictureGroupRepository;
import cn.netkiller.repository.model.DiscoveryPictureGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class PictureGroupService {
@Autowired
private PictureGroupRepository pictureGroupRepository;
public Page<DiscoveryPictureGroup> discoveryPictureGroup(Pageable pageable) {
return pictureGroupRepository.discoveryPictureGroup(pageable);// UpIsNotNull
}
}
package cn.netkiller.repository;
import cn.netkiller.domain.story.PictureGroup;
import cn.netkiller.repository.model.DiscoveryPictureGroup;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PictureGroupRepository extends CrudRepository<PictureGroup, Integer> {
Page<PictureGroup> findByOrderByUpDescIdDesc(Pageable pageable);
@Query(value = "select pg.id, pg.name, p.thumbnail,pg.up from picture_group pg, picture p where pg.id = p.picture_group_id group by pg.id order by pg.up desc, pg.id desc", nativeQuery = true)
Page<DiscoveryPictureGroup> discoveryPictureGroup(Pageable pageable);
}
package cn.netkiller.repository.model;
public interface DiscoveryPictureGroup {
Integer getId();
String getName();
String getThumbnail();
Integer getUp();
}
@GetMapping("discovery/group")
// @Deprecated(since = "v4.0.0", forRemoval = true)
public AigcResponse group(Pageable pageable) {
Page<Map<String, Object>> page = pictureGroupService.discoveryPictureGroup(pageable);
List<Map<String, Object>> newList = new ArrayList<Map<String, Object>>();
List<Map<String, Object>> contents = page.getContent();
contents.forEach(content -> {
Map<String, Object> map = new HashMap<String, Object>(content);
map.put("thumbnail", url.concat((String) map.get("thumbnail")));
newList.add(map);
});
Page<Map<String, Object>> newPage = new PageImpl<>(newList, pageable, page.getTotalElements());
return new AigcResponse(newPage);
}
package cn.netkiller.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import java.io.Serial;
import java.io.Serializable;
@Entity
@DynamicInsert
@DynamicUpdate
@Table
@Data
public class Follow implements Serializable {
@Serial
public static final long serialVersionUID = -1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false, columnDefinition = "int unsigned")
@Comment("主键")
private Integer id;
@ManyToOne
@JoinColumn(name = "follower")
private Consumer follower;
@ManyToOne
@JoinColumn(name = "followed")
private Consumer followed;
}
package cn.netkiller.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table
@Data
@DynamicUpdate
@DynamicInsert
@Comment("客户信息表")
public class Consumer implements Serializable {
@Serial
public static final long serialVersionUID = -1L;
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY)
// @Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false, columnDefinition = "int unsigned")
@Comment("唯一ID")
// @Column(name = "id", nullable = false, columnDefinition = "int unsigned")
private Integer id;
@OneToOne(cascade = CascadeType.MERGE)
@MapsId
@JoinColumn(name = "id", insertable = true, updatable = false, columnDefinition = "int unsigned", foreignKey = @ForeignKey(name = "device_id"))
@JsonIgnore
private Device device;
@Comment("姓名")
@Column(length = 8)
private String name;
@Comment("昵称")
@Column(length = 16)
private String nickname;
@Comment("头像")
private String avatar;
@Comment("性别")
private Boolean gender;
@Comment("年龄")
private Integer age;
@Comment("生日")
@JsonFormat(pattern = "yyyy-MM-dd")
@Temporal(TemporalType.DATE)
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@Comment("地址")
private String address;
@Comment("电话")
@Column(length = 15)
private String mobile;
@Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
@Comment("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date ctime;
@Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
@Comment("修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date mtime;
public Consumer() {
}
}
package cn.netkiller.service;
import cn.netkiller.domain.Consumer;
import cn.netkiller.domain.Follow;
import cn.netkiller.repository.FollowRepository;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class FollowService {
@Autowired
private FollowRepository followRepository;
public Iterable<Follow> following() {
Iterable<Follow> follow = followRepository.findAll();
return follow;
}
public void following(Integer follower, Integer followed) {
Follow follow = new Follow();
Consumer me = new Consumer();
me.setId(follower);
Consumer their = new Consumer();
me.setId(followed);
followRepository.findByFollowerAndFollowed(me, their);
follow.setFollower(me);
follow.setFollower(their);
followRepository.save(follow);
}
@Transactional
public boolean follow(Integer follower, Integer followed) {
// Consumer follower = new Consumer();
// follower.setId(follow.getFollower().getId());
//
// Consumer followed = new Consumer();
// followed.setId(follow.getFollowed().getId());
followRepository.followByFollowerAndFollowed(follower, followed);
return true;
}
@Transactional
public boolean unfollow(Integer follower, Integer followed) {
// Consumer follower = new Consumer();
// follower.setId(follow.getFollower().getId());
//
// Consumer followed = new Consumer();
// followed.setId(follow.getFollowed().getId());
followRepository.deleteByFollowerAndFollowed(follower, followed);
return true;
}
}
package cn.netkiller.controller;
import cn.netkiller.ai.AigcJsonResponse;
import cn.netkiller.domain.Follow;
import cn.netkiller.service.FollowService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@Slf4j
@RequestMapping("/follow/{appid}/{device}")
public class FollowController {
@Autowired
private FollowService followService;
@GetMapping("following")
public Iterable<Follow> following() {
Iterable<Follow> follow = followService.following();
return follow;
}
@PutMapping("follow/{follower}/{followed}")
public AigcJsonResponse follow(@PathVariable("follower") Integer follower, @PathVariable("followed") Integer followed) {
followService.follow(follower, followed);
return new AigcJsonResponse(true);
}
@DeleteMapping("unfollow/{follower}/{followed}")
public AigcJsonResponse unfollow(@PathVariable("follower") Integer follower, @PathVariable("followed") Integer followed) {
boolean status = followService.unfollow(follower, followed);
return new AigcJsonResponse(status);
}
}
package cn.netkiller.repository;
import cn.netkiller.domain.Consumer;
import cn.netkiller.domain.Follow;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface FollowRepository extends CrudRepository<Follow, Integer> {
Optional<Follow> findByFollowerAndFollowed(Consumer me, Consumer their);
@Modifying
// @Query(value = "DELETE FROM Follow WHERE follower=:follower and followed=:followed", nativeQuery = true)
@Query(value = "DELETE FROM Follow WHERE follower.id=:follower and followed.id=:followed")
void deleteByFollowerAndFollowed(@Param("follower") Integer follower, @Param("followed") Integer followed);
@Query(value = "INSERT INTO Follow(follower,followed) VALUES( :follower,:followed)", nativeQuery = true)
void followByFollowerAndFollowed(Integer follower, Integer followed);
}
原生写法:@Query(value = "DELETE FROM Follow WHERE follower=:follower and followed=:followed", nativeQuery = true)
JPQL写法:@Query(value = "DELETE FROM Follow WHERE follower.id=:follower and followed.id=:followed")
两种写法结果相同
返回集合
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
处理子查询 IN
@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);
返回 0 表示更新失败,返回 1 表示更新成功
@Query("UPDATE Picture SET share = :status WHERE id=:id")
@Modifying
int updateShareStatus(@Param("id") Long id, @Param("status") boolean status);
interface UserRepository extends Repository<User, Long> {
// Plain query method
@Lock(LockModeType.READ)
List<User> findByLastname(String lastname);
package cn.netkiller.repository;
import cn.netkiller.domain.PictureClick;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface PictureClickRepository extends CrudRepository<PictureClick, Integer> {
@Query("SELECT SUM(favorites) AS favorites FROM PictureClick WHERE device.id = :deviceId")
Optional<Integer> query(@Param("deviceId") Integer deviceId);
@Query(value = "SELECT sum(quantity) FROM Product")
public Long sumQuantities();
@Query(value = "SELECT sum(quantity * price) FROM Product")
public BigDecimal total();
}
@Query("SELECT SUM(favorites) AS favorites, SUM(likes) AS likes FROM PictureClick WHERE device.id = :deviceId")
Optional<List<Integer[]>> query(@Param("deviceId") Integer deviceId);
@Query("SELECT new map(SUM(favorites) AS favorites, SUM(likes) AS likes) FROM PictureClick WHERE device.id = :deviceId")
Optional<Map<String, Integer>> query(@Param("deviceId") Integer deviceId);
下面介绍一下@Transactional注解的参数以及使用:
事物传播行为介绍:
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
事物超时设置:
@Transactional(timeout=30) //默认是30秒
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化 MYSQL: 默认为REPEATABLE_READ级别 SQLSERVER: 默认为READ_COMMITTED
@Transactional注解中常用参数说明
注意的几点:
@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 元素的出现 开启 了事务行为。
Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。
在SimpleJpaRepository中save()已经帮我们实现
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!";
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
private final PersistenceProvider provider;
private @Nullable CrudMethodMetadata metadata;
/**
* Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}.
*
* @param entityInformation must not be {@literal null}.
* @param entityManager must not be {@literal null}.
*/
public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
Assert.notNull(entityManager, "EntityManager must not be null!");
this.entityInformation = entityInformation;
this.em = entityManager;
this.provider = PersistenceProvider.fromEntityManager(entityManager);
}
/**
* Creates a new {@link SimpleJpaRepository} to manage objects of the given domain type.
*
* @param domainClass must not be {@literal null}.
* @param em must not be {@literal null}.
*/
public SimpleJpaRepository(Class<T> domainClass, EntityManager em) {
this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
}
/**
* Configures a custom {@link CrudMethodMetadata} to be used to detect {@link LockModeType}s and query hints to be
* applied to queries.
*
* @param crudMethodMetadata
*/
public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.metadata = crudMethodMetadata;
}
@Nullable
protected CrudMethodMetadata getRepositoryMethodMetadata() {
return metadata;
}
//......
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Transactional
public void deleteById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Transactional
public void delete(T entity) {
Assert.notNull(entity, "The entity must not be null!");
em.remove(em.contains(entity) ? entity : em.merge(entity));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Transactional
public void deleteAll(Iterable<? extends T> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
for (T entity : entities) {
delete(entity);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable)
*/
@Transactional
public void deleteInBatch(Iterable<T> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
if (!entities.iterator().hasNext()) {
return;
}
applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em)
.executeUpdate();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.Repository#deleteAll()
*/
@Transactional
public void deleteAll() {
for (T element : findAll()) {
delete(element);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch()
*/
@Transactional
public void deleteAllInBatch() {
em.createQuery(getDeleteAllQueryString()).executeUpdate();
}
//......
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#saveAndFlush(java.lang.Object)
*/
@Transactional
public <S extends T> S saveAndFlush(S entity) {
S result = save(entity);
flush();
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#save(java.lang.Iterable)
*/
@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#flush()
*/
@Transactional
public void flush() {
em.flush();
}
//......
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findById(java.io.Serializable)
*/
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable)
*/
@Override
public T getOne(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
/**
* Applies the given {@link Specification} to the given {@link CriteriaQuery}.
*
* @param spec can be {@literal null}.
* @param domainClass must not be {@literal null}.
* @param query must not be {@literal null}.
* @return
*/
private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass,
CriteriaQuery<S> query) {
Assert.notNull(domainClass, "Domain class must not be null!");
Assert.notNull(query, "CriteriaQuery must not be null!");
Root<U> root = query.from(domainClass);
if (spec == null) {
return root;
}
CriteriaBuilder builder = em.getCriteriaBuilder();
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
return root;
}
//......
}
package cn.netkiller.api.repository;
import javax.transaction.Transactional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import cn.netkiller.api.domain.RecentRead;
@Repository
public interface RecentReadRepostitory extends CrudRepository<RecentRead, Integer> {
Page<RecentRead> findByMemberIdOrderByIdDesc(int memberId, Pageable pageable);
int countByMemberId(int memberId);
@Transactional
@Modifying
@Query("DELETE FROM RecentRead r WHERE r.memberId = ?1 AND r.articleId = ?2")
void deleteByMemberIdAndArticleId(int memberId, int articleId);
@Transactional
@Modifying
@Query("delete from RecentRead where member_id = :member_id")
public void deleteByMemberId(@Param("member_id") int memberId);
int countByMemberIdAndArticleId(int memberId, int articleId);
}
// 指定Exception回滚
@Transactional(rollbackFor=Exception.class)
public void methodName() {
// 不会回滚
throw new Exception("...");
}
//指定Exception回滚,但其他异常不回滚
@Transactional(noRollbackFor=Exception.class)
public ItimDaoImpl getItemDaoImpl() {
// 会回滚
throw new RuntimeException("注释");
}
@Service
public class UserService {
@Autowired
private UserRepostitory userRepostitory;
@Transactional
public void add(User user) {
try {
userRepostitory.save(user);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// 不会回滚
}
@Transactional
public void add(User user) throws Exception {
try {
userRepostitory.delete(user);
} catch (Exception e) {
log.error(e.getMessage(), e);
// 抛出异常才会回滚
throw new Exception(e);
}
}
}
@Transactional 必须与 public 一起使用,不能定义为 private、default、protected
@Service
public class UserService {
@Transactional
private void add(User user) {
save(user);
}
}
this 调用不支持事物
@Service
public class UserService {
@Autowired
private UserRepostitory userRepostitory;
@Transactional
public void add(User user) {
userRepostitory.save(user);
this.update(user);
}
@Transactional
public void update(User user) {
userRepostitory.update(user);
}
}
解决方案
@Servcie
public class UserService {
@Autowired
prvate ProfileService profileService;
public void save(User user) {
profileService.save(user);
}
}
@Servcie
public class ProfileService {
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
}
}
@Transactional 需要在 @Controller、@Service、@Component、@Repository 等注解下才能使用
// @Servcie
public class UserService {
@Autowired
prvate ProfileService profileService;
@Transactional
public void save(User user) {
profileService.save(user);
}
}
屏蔽 @Servcie 后观察 save 的 @Transactional 是不生效的。
@Service
public class UserService {
@Autowired
private UserRepostitory userRepostitory;
@Autowired
private RoleService roleService;
@Transactional
public void add(User user) throws Exception {
userRepostitory.save(user);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
...
...
}
}
@Transactional
public void test(SysConfigEntity entity) throws Exception {
//不会回滚
this.saveSysConfig(entity);
throw new Exception("rollback test");
}
@Transactional(rollbackFor = Exception.class)
public void test1(SysConfigEntity entity) throws Exception {
//会回滚
this.saveSysConfig(entity);
throw new Exception("rollback test");
}
@Transactional
public void test2(SysConfigEntity entity) throws Exception {
//会回滚
this.saveSysConfig(entity);
throw new RuntimeException("rollback test");
}
@Transactional
public void test3(SysConfigEntity entity) throws Exception {
//事务仍然会被提交
this.test4(entity);
throw new Exception("rollback test");
}
@Transactional(rollbackFor = Exception.class)
public void test4(SysConfigEntity entity) throws Exception {
this.saveSysConfig(entity);
}
package cn.netkiller.repository;
import cn.netkiller.domain.device.NotificationStatus;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface NotificationStatusRepository extends CrudRepository<NotificationStatus, Integer> {
@Query(value = "SELECT count(n) FROM Notification n WHERE n.id NOT IN (SELECT ns.notification.id FROM NotificationStatus ns, Consumer c WHERE ns.consumer.id = c.id AND c.wechat = :wechat)")
int queryCountByWechat(@Param("wechat") String wechat);
@Modifying
@Query(value = "INSERT INTO notification_status(consumer_id,notification_id) VALUES( :consumerId, :notificationId)", nativeQuery = true)
void read(@Param("consumerId") int consumerId, @Param("notificationId") int notificationId);
}