I am storing playlist information in an SQLite database. This is my entity class;
@Data
@Builder
public class Playlist {
private String id;
private String playlistName;
private PlaylistType type;
}
The PlaylistType is an enum of ("TYPE_A", "TYPE_B")
At first, in my mapper XML, I tried using resultType="Playlist" and it worked when database column names and Playlist properties were in the same order. But if I change the order of properties, for example as shown below,
public class Playlist {
private String id;
private PlaylistType type; // just moved PlaylistType property
private String playlistName;
}
It is giving me this error:
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'name' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.example.demo.PlaylistType.p1
Then some threads suggested to use resultMap, but the issue persists;
This is my mapper XML using resultMap
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.PlaylistRepository">
<resultMap id="toPlaylist" type="com.example.demo.Playlist">
<id column="id" property="id"/>
<result column="name" property="playlistName"/>
<result column="type" property="type"/>
</resultMap>
<select id="findById" resultMap="toPlaylist">
select * from playlist where id = #{id}
</select>
</mapper>
Is there a better way to map database columns? It shouldn't be too restrictive in silly matters like the order of properties in the entity class. Or am I missing something?
Adding
@NoArgsConstructoris the easiest solution.This answer is for people who don't want to add no-args constructor to an immutable class.
When you add
@Builderto the class, Lombok generates the following constructor.Although it is package-private, MyBatis can (has to; because there is no other constructor) use this constructor using reflection [1].
To map the result to this constructor, MyBatis provides four different methods.
<constructor>withoutnameattribute<constructor>withnameattributetl;dr
Method 2 should be sufficient for most simple cases.
Use method 3 or 4 for advanced mapping or when you need the best performance.
1. Order-based constructor auto-mapping
This is the default behavior when you don't use
<resultMap>.In your example, the constructor takes three arguments, so the first, second and third columns in the result set are mapped to
id,playlistNameandtyperespectively.When you change the field order in the class, the order of the constructor arguments changes and you have to change the column order as well.
Personally, I do not recommend this order-based constructor auto-mapping because there is a known issue that can be a head-scratcher.
I explained it in this answer if you are interested.
2. Arg-name-based constructor auto-mapping
This is the behavior when you 1) enable
argNameBasedConstructorAutoMappingin the config and 2) don't use<resultMap>. With this method, MyBatis looks for a column that has the same name as the constructor argument [2].The column order does not matter.
Note that, in your example, the column name
namedoes not match the target argument nameplaylistName, so you may have to specify a column alias in the SELECT statement.argNameBasedConstructorAutoMappingwas added in version 3.5.10.3. Result map with
<constructor>withoutnameattributeWhen using a result map, you need
<constructor>,<idArg>and<arg>elements to perform constructor mapping.For the sake of completeness, here is the same result map declared in a Java mapper [3].
With this method, the column order does not matter, but the XML element order must match the constructor argument order.
In case it is difficult for you to maintain the XML element order (e.g. the target class is frequently updated), see the next section.
4. Result map with
<constructor>withnameattributeWhen
nameattribute is specified, the order of XML elements does not have to match the order of the actual constructor arguments [4].In your case, the constructor argument types always match the field types, so
javaTypecan be omitted.And here is the same result map using annotation.
With the above result map, MyBatis searches a constructor that has the following three arguments, but in arbitrary order.
id, type=java.lang.StringplaylistName, type=java.lang.Stringtype, type=pkg.PlaylistTypeWhen there are multiple constructors that match the criteria, you need to add
@AutomapConstructorto the right one.Once the constructor is found, the value of the specified column is mapped to each constructor argument.
So, with this method, both XML element order and column order do not matter, but you may need to edit the
namevalue if you change a field name.This method requires version 3.4.3 or later.
[1] If you use the Java Platform Module System (JPMS), you may have to allow MyBatis to access this constructor.
[2] To include argument names in the binary, you must either 1) specify
-parameterscompiler option or 2) add@Paramannotation to each argument.[3] If you use a version older than 3.5.4, you may need
@ConstructorArgs.[4]
<idArg>must be written before<arg>because it is enforced by the DTD.