A method to double the SQL performance of ORM system

ORM is indeed very convenient and people do not need to write a lot of SQL, but many people criticize the poor performance of SQL

For example, a function is to modify the gold coins of get user data to zero and then update

A very simple business:

 

User user=dao.getUser(uid);
user.setGold(0);
dao.updateUser(user);

 Such a simple function will actually send a long string of SQL update statements to the database

 

如:update user set a=?,b=?,c=?............gold=? where uid = ?

Seeing this, someone will understand where the problem lies. Think about the fact that each table has a bunch of fields. In fact, we only modify one or two fields in each business, resulting in such a long list of business calls each time. SQL, which puts enormous and unnecessary pressure on the database.

 

Some people will say: My programming habits are very good, and I usually write sql separately for methods that are called frequently. For example, to modify the gold coins above, a separate updateGold method will be written. Of course, this is a good habit and will solve this problem as well. But most developers, most business implementations don't do this. isn't it?

 

Now there is another way to optimize the original ORM system and take into account the development efficiency and system performance improvement:

 

	/** Compare before update, only update modified columns*/
	public <T> void compareAndUpdate(T oldT, T newT);

 A brief introduction is that by comparing the old and new entities, the specified columns are generated according to the modified columns to achieve the purpose of targeted update.

 

 

//before:
User user=dao.getUser(uid);
user.setGold(0);
dao.updateUser(user);
// Generated sql:
update user set a=?,b=?,c=?............gold=? where uid = ?

//New approach:
User user=dao.getUser(uid);
User oldUser=BeanUtils.cloneBean(user);
user.setGold(0);
dao.compareAndUpdate(oldUser,user);
//generated sql:
update user set gold = ? where uid = ?

The shortening of the sql statement exponentially reduces the overhead of communication with the database, and greatly reduces the pressure on the database.

If the above way of writing may be a little troublesome, we can be a little lazy:
User user=dao.getUser(uid);
Object oldBean=BeanUtils.copyBean(user);//The difference is that you can copy this code, you don't need to modify the type of oldBean frequently. Because we don't care what type oldBean is.
user.setGold(0);
dao.compareAndUpdate(oldBean,user);

 Some people will say that entity copying and splicing SQL will also generate overhead, which is good. But this kind of overhead is simply a drop in the bucket for the pressure on the database.

 

 

The compareAndUpdate method is generated by rewriting the update method in the ORM framework I wrote: freyja-jdbc. If you are using other ORM frameworks that have reserved interfaces, you can rewrite them. If there is no interface, you may need to implement it yourself. The method is to map entities and fields to generate sql statements

I haven't touched hibernate for a long time. It is not clear how to rewrite hibernate to achieve the same effect. If you are interested, you can study it yourself.

 

Finally, let's put down the relevant code.

	public static Parameter compareAndUpdate(Object oldEntity, Object newEntity) {
		FreyjaEntity entity = ShardingUtil.getEntity(newEntity.getClass());
		List<Object> args = new ArrayList<Object>();
		Object idValue = null;

		BeanMap oldBeanMap = BeanMap.create(oldEntity);

		BeanMap beanMap = BeanMap.create(newEntity);

		List<String> columnNameList = new ArrayList<String>();
		for (Property p : entity.getProperties().values()) {
			ShardingProperty s = (ShardingProperty) p;
			Object newPropertyValue = beanMap.get(p.getName());
			if (s.isId()) {
				idValue = newPropertyValue;
				continue;
			} else {
				Object oldPropertyValue = oldBeanMap.get(p.getName());
				if ((newPropertyValue == null && oldBeanMap == null)
						|| newPropertyValue.equals(oldPropertyValue)) {// The value has not changed, not updated
					continue;
				}
			}
			columnNameList.add(p.getName());
			args.add(newPropertyValue);
		}
		args.add(idValue);

		Parameters parameter = null;

		if (entity.isSubTable()) {
			DbResult result = ShardingUtil.engine.getShardingStrategy()
					.getShardingTableNameById(entity.getTableName(), idValue);
			parameter = entity.updateByColumn(columnNameList, idValue);

			parameter.setDbNo(result.getDbNo());
		} else {
			parameter = entity.updateByColumn(columnNameList, null);
		}
		parameter.setArgs(args.toArray());
		return parameter;
	}

	/** Produce related information according to the specified column */
	public Parameter updateByColumn(List<String> columnNameList, Object idValue) {
		String set = "";
		List<Integer> types = new ArrayList<Integer>();
		for (String cn : columnNameList) {
			ShardingProperty s = (ShardingProperty) getProperties().get(cn);
			set += s.getDbColumnName() + " = ? ,";
			types.add(s.getTypes());
		}

		set = set.substring(0, set.length() - 1);
		String setSql = " set " + set + " where " + getId().getDbColumnName()
				+ " = ?";
		types.add(getId().getTypes());
		String sql = null;
		if (idValue == null) {
			sql = "update " + getTableName() + setSql;
		} else {// branch library
			DbResult result = ShardingUtil.engine.getShardingStrategy()
					.getShardingTableNameById(getTableName(), idValue);
			sql = "update " + result.getTableName() + setSql;
		}
		Parameter p = new Parameter();
		p.setSql(sql);
		p.setSqlTypes(ListUtil.toPrimitive(types));
		return p;
	}

 

 ----------------------------------------------------------------------------

After the actual operation, I found that although it is really good, it is different from the existing system and writing method. Is there a smarter way of writing that is compatible with the existing system?

Then I thought of another way:

public class PersistObj implements Serializable {

	/** Used to update compareAndUpdate, and the property will not be serialized */
	private transient  Object oldBean;
get();set();
}

//Then let your entity inherit this object, so that there will be an oldBean property by default to store oldBean
// write another general method

	@Override
	public <T> T getAndClone(Class<T> clazz, Object id) {

		T t = super.get(clazz, id);

		if (t instanceof PersistObj) {
			PersistObj obj = (PersistObj) t;
			Object oldBean = BeanUtils.cloneBean(t);
			obj.setOldBean (oldBean);
		}

		return t;
	}

//The oldBean is stored when querying in this way.

//The previous update method is rewritten as follows:
		if (oldT == null) {
			if (newT instanceof PersistObj) {
				PersistObj obj = (PersistObj) newT;
				oldT = (T) obj.getOldBean();
			}
		}

Let oldBean be fetched from the oldBean property. No need to pass anymore

 

The effect of doing this is:

		User user = userDao.getAndClone(uid);
		user.setGold(0);
		userDao.compareAndUpdate(user);

// This is written here to let everyone understand how it works.
//In fact, these methods can replace the underlying encapsulation, and the business code does not need to be modified.
//That is to say, this writing effect can be achieved:

User user = userDao.getUser(uid);
user.setGold(0);
userDao.updateUser(user);

//Is it the same as before, improving performance without affecting existing code

After doing so, there is no need to modify the original business, because these dao methods are all low-level. Just replace it. The service business method does not need to be changed.

 

Of course, this has 2 disadvantages:

1. It is not valid for the cache. 

 2. Each query will be cloned again.

 

 

For the first question: when the cache is used, only the previous method, manual cloneBean, can be used.

For the second question: First of all, it has no effect on entities that do not need this function without the getAndClone method.

For other general our query is definitely for modification. Most of the logic is like this. So the default situation has little effect. If it is clear that no modification is involved, you can use other default get() methods without using the getAndClone method . It doesn't have much impact, just one more choice when writing

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326205232&siteId=291194637