Skip to content

ResultSet Mapper allows you to map a ResultSet to a desired java object by passing in its type.

License

Notifications You must be signed in to change notification settings

jzheng2017/resultset-mapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Maven Central Build Status Coverage Status Language grade: Java Maintainability

ResultSet Mapper

ResultSet Mapper is a small lightweight library that allows you to map a ResultSet object to a desired java object by passing in its type.

Example usages

Model object

Define a java object you want to map to. Use the @Column annotation to override the FieldNamingStrategy

public class User {
    private int id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    private String email;
}

Map ResultSet to desired model object

To map a ResultSet you need an instance of ResultSetMapper. The library provides a factory class ResultSetMapperFactory that serves ResultSetMapper with different FieldNamingStrategy. The ResultSetMapper defaults to the IdentityFieldNamingStrategy.

// ResultSetMapper with IdentityFieldNamingStrategy
ResultSetMapper r = ResultSetMapperFactory.getResultSetMapperIdentity(); 
List<User> users = r.map(resultSet, User.class);
// more examples of out of the box factory calls for different field naming strategies

// ResultSetMapper with LowerCaseUnderscoreFieldNamingStrategy
ResultSetMapper r2 = ResultSetMapperFactory.getResultSetMapperLowerCaseUnderscore(); 
List<User> users = r2.map(resultSet, User.class);

// ResultSetMapper with LowerCaseDashesFieldNamingStrategy
ResultSetMapper r3 = ResultSetMapperFactory.getResultSetMapperLowerCaseDashes(); 
List<User> users = r3.map(resultSet, User.class);

Field naming strategies

The library provides out of the box a few field naming strategies.

Note: The provided strategies assumes you use camelCase for your field names. It does not work if you use other naming styles. If this does not conform to your naming style, you have to implement your own field naming strategy.

IdentityFieldNamingStrategy

This strategy leaves the field names unchanged.

LowerCaseUnderscoreFieldNamingStrategy

This strategy maps the field names to lowercase with underscores. For instance firstName would map to first_name.

LowerCaseDashesFieldNamingStrategy

This strategy maps the field names to lowercase with dashes. For instance firstName would map to first-name.

Custom field naming strategy

It is possible to make your own field naming strategy. It is done by implementing the FieldNamingStrategy interface. It has one function transform which transforms the original field name. The concrete FieldNamingStrategy implementation can then be injected through the constructor.

ResultSetMapper r = new ResultSetMapper(new CustomFieldNamingStrategy());

Attribute converting

The library provides the ability to convert one type to another, allowing two incompatible types to work during mapping.

@Convert annotation

Using the @Convert annotation you can define on class field level which attribute converter must be used.

Note: If there is no @Convert annotation is present, the mapper will search for an appropriate attribute converter. If it has found one it will apply only if there is a @Converter annotation applied on the attribute converter with autoApply equaling true.

public class User {
    private int id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    private String email;
    @Convert(converter = TimestampToLocalDateTimeConverter.class)
    private LocalDateTime creationDateTime;
}

Attribute converter

Let's see how an attribute converter can be defined. As you can see below an attribute converter can be defined by implementing the AttributeConverter<S, T> interface.

@Converter(autoApply = true)
public class TimestampToLocalDateTimeConverter implements AttributeConverter<Timestamp, LocalDateTime> {

    @Override
    public LocalDateTime convert(Timestamp value) {
        return value.toLocalDateTime();
    }

    @Override
    public Class<Timestamp> source() {
        return Timestamp.class;
    }

    @Override
    public Class<LocalDateTime> target() {
        return LocalDateTime.class;
    }
}

This interface has three methods.

  • convert - the actual method that does the conversion of type S to type T.
  • source - source class, it should be of type S.
  • target - target class, it should be of type T.

The source and target is used internally to determine if the attribute converter is appropriate to be used for a field.

An attribute converter must always be annotated with the @Converter annotation to let the mapper know it is an attribute converter. The annotation can accept one value which is autoApply. autoApply determines whether to automatically apply the converter when possible. The default value is false

Available attribute converters

The library currently provides a few out of the box attribute converters.

  • TimestampToLocalDateTimeConverter - converts a value of type java.sql.Timestamp to java.time.LocalDateTime
  • DateToLocalDateConverter - converts a value of type java.sql.Date to java.time.LocalDate
  • TimeToLocalTimeConverter - converts a value of type java.sql.Time to java.time.LocalTime

You can also define your own attribute converters by implementing the AttributeConverter<S, T> interface. After having defined it you must register this attribute converter. You can register it by calling the registerAttributeConverter on the ResultSetMapper.

Ignoring fields

The library allows you to annotate class fields with @Ignore. This annotation can be used on a field to let the ResultSetMapperknow that it can skip this field when mapping. This means that the ResultSetMapper will not try to retrieve the value from the ResultSet for the annotated field.

Why would I ignore a field?

Let me show you an example.

public class User {
    private int id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    private String email;
    @Ignore
    private List<UserPermission> userPermissions;
}

As you can see above the User class has an additional List<UserPermission> object. To retrieve that it requires an additional query and can not be mapped when mapping the initial ResultSet. By using the @Ignore annotation it can let the mapper know that it does not have to be mapped when trying to map the User class.

Logging

The library has very extensive logging at every logging level. From TRACE to ERROR, the logging level is default set on INFO. This means only logging messages with log level of INFO and above will be logged.

It is possible to suppress warnings for particular classes. For instance the mapper will throw warning messages when a field can not be found in the ResultSet. It would be annoying to be spammed with warning message for every object that is to be mapped. The library provides the @SuppressWarnings annotations to "suppress" these warning messages.

Class level @SuppressWarnings

@SuppressWarnings
public class User {
    private int id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    private String email;
}

This will suppress all warnings for every field in the annotated class.

Field level @SuppressWarnings

public class User {
    private int id;
    @SuppressWarnings
    @Column(name = "first_name")
    private String firstName;
    @SuppressWarnings
    @Column(name = "last_name")
    private String lastName;
    private String email;
}

It is also possible to suppress warnings at field level. The warnings will be suppressed for that particular annotated field.

Why use this library?

This library makes it easy to map to a ResultSet to your desired java model object. It can be done with only 1 line of code! It saves you a lot of duplicate code when mapping every query.

List<User> users = new ArrayList();

while (resultSet.next()){
    User user = new User();
    user.setId(resultSet.getInt("id");
    user.setFirstName(resultSet.getString("first_name"));
    user.setLastName(resultSet.getString("last_name"));
    user.setEmail(resultSet.getString("email"));
    
    users.add(user);
}

vs

List<User> users = r.map(resultSet, User.class);

Way more cleaner, right? Imagine doing the first example for every different query, that would be a lot of code..

The library also takes care of all the exception handling and provides very extensive logging, making it very easy to spot errors when it occurs.

Installation

Maven

<dependency>
  <groupId>nl.jiankai</groupId>
  <artifactId>resultset-mapper</artifactId>
  <version>1.6.2</version>
</dependency>

Gradle

implementation 'nl.jiankai:resultset-mapper:1.6.2'

License

See the LICENSE file for the license rights and limitations (MIT).

Java version

The library uses Java 11.

About

ResultSet Mapper allows you to map a ResultSet to a desired java object by passing in its type.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages