The Counter Class
The Entity bellow allow us to implement sharded counters for almost anything :- count :
- this attribute will handle the counter value for this sharded counter
- refId :
- the ID of the Entity this sharded counter is used for
- entityClass :
- the Entity class this sharded counter is used for
- type :
- what this counter is counting
@Entity
@SuppressWarnings("serial")
public class Counter implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long count;
private Long refId;
private int type;
private String entityClass;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
public Long getRefId() {
return refId;
}
public void setRefId(Long refId) {
this.refId = refId;
}
public String getEntityClass() {
return entityClass;
}
public void setEntityClass(String entityClass) {
this.entityClass = entityClass;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
The service and how to increment a counter
In order to increment the number of time a web page is viewed, we would like to do something like :incrementCounter(WebPage.class, webPageId, WebPage.VIEWS)Where webPageId is the id of the WebPage Entity and WebPage.VIEW a constant.
The incrementCounter method will work as follow :
- Defines a MAX_TRIES values to store the maximum number of time we will try to update an existing sharded counter
- Retrives the list of the sharded counter already persisted for the given type
- If none exists, a new sharded counter with a value of 1 is persisted for the given type, the method returns
- Else, one sharded counter is picked up at random and its value is incremented
- If the update fails, the number of remaining tries is decremented
- If there is no try left, a new sharded counter with a value of 1 is persisted for the given type, the method returns
- Else, start again at step 2
/**
* Sum all the counter for the given type and entity
* @param c
* @param refId
* @param type
* @return
*/
protected long counterSum(Class<?> c, Long refId, int type) {
long sum = 0;
List<Counter> counters = getCounters(c, refId, type);
for (Counter counter : counters) {
sum += counter.getCount();
}
return sum;
}
/**
* Get all the counter for the given type and entity
* @param c
* @param refId
* @param type
* @return
*/
private List<Counter> getCounters(Class<?> c, Long refId, int type) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("refId", refId);
params.put("type", type);
params.put("entityClass", c.getSimpleName());
return list(Counter.class,
"SELECT c FROM Counter c WHERE refId = :refId AND type = :type AND entityClass = :entityClass",
params);
}
protected void incrementCounter(Class<?> c, Long refId, int type) {
modifyCounter(c, refId, type, 1);
}
protected void decrementCounter(Class<?> c, Long refId, int type) {
modifyCounter(c, refId, type, -1);
}
/**
* Modify the counter value for the given type and entity
* @param c
* @param refId
* @param type
* @param step
*/
protected void modifyCounter(Class<?> c, Long refId, int type, int step) {
int tries = MAX_TRIES;
EntityManager em = getEntityManager();
while (true) {
try {
List<Counter> counters = getCounters(c, refId, type);
if (counters.size() == 0) {
newCounter(c, refId, type, step);
break;
}
try {
em.getTransaction().begin();
Random generator = new Random();
int counterNum = generator.nextInt(counters.size());
Counter counter = counters.get(counterNum);
counter.setCount(counter.getCount() + step);
em.merge(counter);
em.getTransaction().commit();
break;
} finally {
if (em != null) {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
} catch (ConcurrentModificationException cme) {
if (--tries == 0) {
newCounter(c, refId, type, step);
break;
}
}
}
}
private void newCounter(Class<?> c, Long refId, int type, int step) {
EntityManager em = null;
try {
em = getEntityManager();
Counter counter = new Counter();
counter.setCount(Long.valueOf(step));
counter.setEntityClass(c.getSimpleName());
counter.setRefId(refId);
counter.setType(type);
em.getTransaction().begin();
em.persist(counter);
em.getTransaction().commit();
} finally {
if (em != null) {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
}
protected final <R> List<R> list(Class<R> c, String query, Map<String, Object> parameters) {
EntityManager em = getEntityManager();
Query select = em.createQuery(query);
for (String key : parameters.keySet()) {
select.setParameter(key, parameters.get(key));
}
List<R> list = select.getResultList();
return list;
}
Enjoy!
Aucun commentaire:
Enregistrer un commentaire