Mybatis-3: Allow omit the 'method' attribute when provider method name is same with mapper method

Created on 15 May 2018  路  9Comments  路  Source: mybatis/mybatis-3

Example

@SelectProvider(type = SqlProvider.class, method = "listAirSensorsByDuidAndTimeRange")
List<AirSensor> listAirSensorsByDuidAndTimeRange();

class SqlProvider {
public static String listAirSensorsByDuidAndTimeRange() {
// return SQL;
}
}

When method name of mapper's and sqlprovider's is the same (in bold), method attribute can be ignored for easy development.
Typing that every time is pretty annoying.

1283

Thank you!

enhancement

Most helpful comment

@harawata I think it's good 馃憤
And as option, we may consider that providing a default implementation of rule base on default method.

public interface SqlProviderMethodResolver {
  default Method resolve(ProviderContext context) {
    // providing default implementation?
  }
}

WDYT?

All 9 comments

I think this is useful. @harawata WDYT?

@kazuki43zoo @harawata this is a good request but I think we can make it even better.
Similar to what we did for constructor injection, we can just pick whatever method matches the provider signature. That would be really a really nice feature cause you can name it as you like without having it string typed in the annotation.

Can this be achieved with the enhancement #1055 ?
i.e. instead of omitting method , you can specify some 'universalMethod' on all methods.

@SelectProvider(type = SqlProvider.class, method = "universalSelect")
List<AirSensor> listAirSensorsByDuidAndTimeRange();

Then in SqlProvider#universalSelect(), you can search the actual method using the method name or signature.

class SqlProvider {
  String universalSelect(ProviderContext context) {
     // look for a method with the same name, etc.
  }
  String listAirSensorsByDuidAndTimeRange() {
     return "select * from ...";
  }
}

I'm not strongly opposed to the change, but there seems to be some overlap in these features.

Thank you for your reply! @harawata
I've tried my best to implement universalSelect method as you suggested, but it only works fine with methods without parameters, the methods with parameters has to be dealt with the universalSelect with an extra java.util.Map instance.

Mapper's methods with and without params

@SelectProvider(type = AirSensorSqlProvider.class, method = "universalSelect")
List<AirSensor> listAirSensorsByDuidAndTimeRange(@Param("duid") Long duid,
        @Param("timeStart") Date timeStart, @Param("timeEnd") Date timeEnd);

@SelectProvider(type = AirSensorSqlProvider.class, method = "universalSelectNoParam")
List<AirSensor> listAllAirSensors();

Methods in Sqlprovider

public String universalSelectNoParam(ProviderContext providerContext) {
    String sql = "";
    try {
        Method mapperMethod = providerContext.getMapperMethod();
        Method providerMethod = this.getClass().getMethod(mapperMethod.getName());
        sql = (String) providerMethod.invoke(this);
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        e.printStackTrace();
    }
    return sql;
}

public String universalSelect(ProviderContext providerContext, Map<String, Object> map) {
    String sql = "";
    String keyPrefix = "param";
    Method mapperMethod = providerContext.getMapperMethod();
    Object[] params = new Object[mapperMethod.getParameterCount()];
    for (Map.Entry entry : map.entrySet()) {
        String key = (String) entry.getKey();
        // detect "param1", "param2" and so on, place them in right order
        if (key.startsWith(keyPrefix)) {
            params[Integer.parseInt(key.substring(keyPrefix.length())) - 1] = entry.getValue();
        }
    }
    try {
        Method providerMethod = this.getClass().getMethod(mapperMethod.getName(), mapperMethod.getParameterTypes());
        // invoke method with parameters.
        sql = (String) providerMethod.invoke(this, params);
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        e.printStackTrace();
    }
    return sql;
}

Those two universalSelect and universalSelectNoParam are running well, but I think it is somehow complex like parameters placed in invoke must be in order.
I hope I do all things right.

Thank you, @picc-lu . You made a good point!

Then, how about introducing a new interface like this?

public interface SqlProviderMethodResolver {
  Method resolve(ProviderContext context);
}

If the class specified in type implements this interface, MyBatis uses the method returned from resolve() as the SQL provider.

This way, you may have to write the name matching rule by yourself, but it allows other users to write their own rules.

@h3adache @kazuki43zoo
Please let me know if you guys think it's over-engineered. :D

@harawata I think it's good 馃憤
And as option, we may consider that providing a default implementation of rule base on default method.

public interface SqlProviderMethodResolver {
  default Method resolve(ProviderContext context) {
    // providing default implementation?
  }
}

WDYT?

I think it's a potentially good change but what I meant was similar to #1055 but having a simple "provide" (or "provideSql"?) as the default method name instead of doing any lookup for name based on classname or anything more complicated. Too simple? Am I missing anything?

@h3adache ,
I'm sorry, I couldn't follow. 馃槄
Could you post some code snippets to help me understand the idea?

@harawata @h3adache @picc-lu

I've tried to fix this. Please check #1499! Thanks.

Was this page helpful?
0 / 5 - 0 ratings