I was looking for a nice-looking way to extract a nullable value from a ResultSet, as opposite to wasNull. Documentation (API spec) guarantees that "untyped" getObject (returning Object) returns null when the column contains NULL, making the following construct valid: (Boolean)rs.getObject(1).
Naturally, I would prefer to do rs.getObject(1, Boolean.class) - but, for some reason, for the typed overload no such guarantees are provided.
Or are they? Maybe I am just missing something? I found nothing for this particular oveload at here , and JDBC spec seems to overlook such overloads entirely.
So, do NULLs get converted to nulls by rs.getObject(1, Boolean.class), or don't they, or is that implementation-dependent, or maybe it depends on some custom conversions defined in an application?
Related
I have this code, which obviously doesn't look nice - it seems all the if-else can somehow be avoided.
if(sortBy.equals("firstName"))
personList.sort(Comparator.comparing(Person::getFirstName));
else if(sortBy.equals("lastName"))
personList.sort(Comparator.comparing(Person::getLastName));
else if(sortBy.equals("age"))
personList.sort(Comparator.comparing(Person::getAge));
else if(sortBy.equals("city"))
personList.sort(Comparator.comparing(Person::getCity));
else if(sortBy.equals("state"))
personList.sort(Comparator.comparing(Person::getState));
else if(sortBy.equals("zipCode"))
personList.sort(Comparator.comparing(Person::getZipCode));
the function takes sortBy, which is the name of one of the attributes of a Person, and applies a sorting to a personList based on that field. How can I avoid the if-else and write a better looking, possibily one line code?
Currently I have found that I can use a HashMap to create a mapping between a field name and a corresponding comparator.
map.put("age", Comparator.comparing(Person::getAge));
map.put("firstName", Comparator.comparing(Person::getFirstName))
...
And use personList.sort(map.get(sortBy)).
But still felt like it can further be improved without an extra step, to the point where it follows the open-closed principle, and adding a new field to Person would not need us to modify the code. I'm looking for something like
personList.sort(Comparator.comparing(Person::getterOfField(sortBy)))
UPDATE-1
For now, I decided to stick with using a Map<String, Function<Person, Comparable<?>> and I do not like to consider reflection based solutions. But still searching if I can find a similar way as this one where sort is a parameter.
UPDATE-2
I think a one-liner is not a good solution, cuz you wouldn't get a compile time error if one of the fields does not implement Comparator.
In general java doesn't want you to work with it this way1; it is not a structurally typed language, and unlike e.g. javascript or python, objects aren't "hashmaps of strings to thingies".
Also, your request more fundamentally doesn't add up: You can't just go from "field name" to "sort on that": What if the field's type isn't inherently sortable (is not a subtype of Comparator<Self>?)
What if there is a column in whatever view we're talking about / config file that is 'generated'? Imagine you have a field LocalDate birthDate; but you have a column 'birth month'2. You can sort on birth month, no problem. However, given that it's a 'generated value' (not backed directly by a field, instead, derived from a calculation based on field(s)), you can't just sort on this. You can't even sort on the backing field (as that would sort by birth year first, not what you want), nor does 'backing field' make sense; what if the virtual column is based on multiple fields?
It is certainly possible that currently you aren't imagining either virtual columns or fields whose type isn't self-sortable and that therefore you want to deposit a rule that for this class, you close the door on these two notions until a pretty major refactor, but it goes to show perhaps why "java does not work that way" is in fact somewhat 'good' (closely meshes with real life concerns), and why your example isn't as boilerplatey as you may have initially thought: No, it is not, in fact, inevitable. Specifically, you seem to want:
There is an exact 1-to-1 match between 'column sort keys' and field names.
The strategy to deliver on the request to sort on a given column sort key is always the same: Take the column sort key. Find the field (it has the same name); now find its getter. Create a comparator based on comparing get calls; this getter returns a type that has a natural sorting order guaranteed.
Which are 2 non-obvious preconditions that seem to have gotten a bit lost. At any rate, a statement like:
if(sortBy.equals("firstName"))
personList.sort(Comparator.comparing(Person::getFirstName));
encodes these 2 non-obvious properties, and trivially, therefore means it is also possible to add virtual columns as well as sort keys that work differently (for example, sorts on birth month, or, sorts on some explicit comparator you write for this purpose. Or even sorts case insensitively; strings by default do not do that, you'd have to sort by String.CASE_INSENSITIVE_COMPARATOR instead.
It strikes me as a rather badly written app if a change request comes in with: "Hey, could you make the sort option that sorts on patient name be case insensitive?" and you go: "Hooo boy that'll be a personweek+ of refactoring work!", no?
But, if you insist, you have 2 broad options:
Reflection
Reflection lets you write code that programatically gets a list of field names, method names, and can also be used to programatically call them. You can fetch a list of method names and filter out everything except:
instance methods
with no arguments
whose name starts with get
And do a simple-ish get-prefix-to-sort-key conversion (basically, .substring(3) to lop off the get, then lowercase the first character, though note that the rules for getter to field name get contradictory if the first 'word' of the field is a single letter, such as getXAxis, where half of the beanspec documents say the field name is definitely XAxis, as xAxis would have become getxAxis, and the other half say it is ambiguous and could mean the field name is XAxis or xAxis).
It looks something like this:
// intentionally raw type!
Map comparators = new HashMap();
for (Method m : Person.class.getMethods()) {
if (Modifiers.isStatic(m.getModifiers()) continue;
if (m.getParameterCount() != 0) continue;
String n = m.getName();
if (!n.startsWith("get") || n.length() < 4) continue;
n = Character.toLowerCase(n.charAt(3)) + n.substring(4);
comparators.put(n, (a, b) -> {
Object aa = m.invoke(a);
Object bb = m.invoke(b);
return ((Comparable) aa).compareTo(bb);
});
}
MyClass.COMPARATORS = (Map<String, Comparator<?>>) Collections.unmodifiableMap(comparators);
Note how this causes a boatload of errors because you just chucked type checking out the window - there is no actual way to ensure that any given getter type actually is an appropriate Comparable. The warnings are correct and you have to ignore them, no fixing that, if you go by this route.
You also get a ton of checked exceptions issues that you'll have to deal with by catching them and rethrowing something appropriate; possibly RuntimeException or similar if you want to disregard the need to deal with them by callers (some RuntimeException is appropriate if you consider any attempt to add a field of a type that isn't naturally comparable 'a bug').
Annotation Processors
This is a lot more complicated: You can stick annotations on a method, and then have an annotation processor that sees these and generates a source file that does what you want. This is more flexible and more 'compile time checked', in that you can e.g. check that things are of an appropriate type, or add support for mentioning a class in the annotation that is an implementation of Comparable<T>, T being compatible with the type of the field you so annotate. You can also annotate methods themselves (e.g. a public Month getBirthMonth() method). I suggest you search the web for an annotation processor tutorial, it'd be a bit much to stuff an example in an SO answer. Expect to spend a few days learning and writing it, it won't be trivial.
[1] This is a largely objective statement. Falsifiable elements: There are no field-based 'lambda accessors'; no foo::fieldName support. Java does not support structural typing and there is no way to refer to things in the language by name alone, only by fully qualified name (you can let the compiler infer things, but the compiler always translates what you write to a fully "named" (package name, type name that the thing you are referring to is in, and finally the name of the method or field) and then sticks that in the class file).
[2] At least in the Netherlands it is somewhat common to split patient populations up by birth month (as a convenient way to split a population into 12 roughly equally sized, mostly arbitrary chunks) e.g. for inviting them in for a checkup or a flu shot or whatnot.
Assuming that the sortBy values and the corresponding getters are known at compile, this would be a good place to use a string switch statement:
Function<Person.String> getter = null;
switch (sortBy) {
case "firstName":
getter = Person::getFirstName; break;
case "lastName":
getter = Person::getLastName; break;
...
}
personList.sort(Comparator.comparing(getter));
If you use a recent version of Java (Java 12 and later) you could use a switch expression rather than a switch statement.
Function<Person.String> getter;
getter = switch (sortBy) {
case "firstName" -> Person::getFirstName;
case "lastName" -> Person::getLastName;
...
default -> null;
}
personList.sort(Comparator.comparing(getter));
Note: you should do a better job (than my dodgy code) of dealing with the case where the sortBy value is not recognized.
As keshlam suggested, I think using the reflection API is the best fitting answer to your question, but keep in mind that using it in production code is generally discouraged.
Note: if you add a new Person-attribute which isn't itself Comparable, you'll have to resort to a custom Comparator anyway. With that in mind, you might want to keep the Map<String, Comparator<?>> solution you already have.
Generally I've been quite a fan of using List.of / Arrays.asList, but unfortunately they don't accept nulls. The usecase I most often stumble upon this, is when dealing with DB calls, and parameters are abstracted to be passed via list. So it always have to be of certain length for any given procedure, and optional values are given as nulls.
I found Why does Map.of not allow null keys and values? which mentions List.of, but mainly talks about maps. And in the case of maps, I agree -- ambiguity of .get seems troublesome (key missing, or value intentionally null?), but this doesn't seem to apply to a list. If you get a null from a list, then you know for sure someone intentionally had put it there.
Some people might say "use optional", but I strongly believe it should be reserved for return types, and others seem to agree Uses for Optional
having an Optional in a class field or in a data structure, is considered a misuse of the API
What is the intended clean, standard solution? Does it really boil down to "use boilerplaty ArrayList initialization without these nice shorthand syntaxes" or "write your own util methods"?
Generally I've been quite a fan of using List.of / Arrays.asList, but unfortunately they don't accept nulls
This is incorrect:
List<String> list = Arrays.asList("a", "b", null);
String nullRef = list.get(2);
System.out.println(nullRef);
Works fine. List.of indeed doesn't accept null refs.
Some people might say "use optional"
Yeah, ignore those people.
What is the intended clean, standard solution?
Clean is a weaselword that is generally best read as: "I like it personally but am unwilling to just say that it's a taste preference" - it means nothing. You can't argue about taste, after all.
The standard solution, that is borderline objective language, we can work with that.
I think your API design that takes a list of db values is the problem. Presumably, you've written your own abstraction layer for this.
I'm having a hard time imaginine DB abstraction APIs where 'list of objects that includes null refs' is the right call.
If you're trying to weave prepared statement 'parameters', this would appear to be a nice API for that, and doesn't require lists at all:
db.select()
.append("SELECT a, b FROM c INNER JOIN d ON c.e = d.f ")
.append("WHERE (signup IS NULL OR signup < ?) ", LocalDate.now())
.append("AND LEFT(username, ?) = ?", 2, "AA")
.query()
If you're trying to abstract an insert/merge/upsert statement, you need some way of linking values with the column name that the value is a value for. This would suggest an API along the lines of:
db.insert("tableName")
.mergeOn("key", key)
.mergeOn("date", someDate)
.put("value", value)
.exec();
You can also re-use Map.of for this purpose, in which case 'I want the db to use the default value for this column' is done by simply not including that key/value pair in your map, thus sidestepping your needs to put null in such a map.
Perhaps if you show your specific API use-case, more insights can be provided.
If your question boils down to:
"I want to put null refs and use List.of, what is the standard clean way to do that"
then the answer is a rather obvious: There is nothing - as that does not work.
There is no --shut-up-List.of--let-me-add-nulls command line switch.
I guess this question is less technical and more philosophical.
I'm writing some Java classes that act as containers of data that could be imported from a variety of formats, including JSON and SQL result sets, both of which could contain null values. These classes share two methods, isNumeric() and isAlphanumeric().
How should these methods treat null values?
Are null values inherently numeric? Are they alphanumeric? I can come up with at least plausible justifications either way. I guess I'm just interested in hearing what the community's opinion might be.
This describes how null is defined in Oracle-
http://www.dbasupport.com/forums/showthread.php?8666-What-is-the-data-type-of-NULL
Null values are neither numeric nor alphanumeric - rather, they are an absence of value (and type).
I read JOOQ API documentation about following APIs, but still couldn't understand what is fieldIndex about.
fetchAny(int fieldIndex, java.lang.Class<? extends T> type)
For example, in the following code I already knows which column to select, why do we need filedIndex of 0? What does 0 mean?
String name = getDslContext().select(TESTB.STU_NAME)
.from(TESTB)
.where(TESTB.ID.eq(studentId))
.fetchAny(0, String.class);
jOOQ mostly operates on Record types. For instance, when you create the following query:
.select(TESTB.STU_NAME)
.from(TESTB)
.where(TESTB.ID.eq(studentId));
You're really operating on a ResultQuery<Record1<String>>. By calling fetchAny(0, String.class) on that type, you're telling jOOQ that you want to fetch any record, and from that record, you want to get only the value at index 0, converted to String.
This may feel like repeating the same information twice (using TESTB.STU_NAME, and fetching the column at index 0). This is because fetchign a single value is quite a special case in the jOOQ API. Unfortunately, the ResultQuery API doesn't really "know" that you're selecting only one column even if that type information is present via generics.
One alternative would be to use DSLContext.fetchValue(ResultQuery):
String name = getDslContext().fetchValue(
DSL.select(TESTB.STU_NAME)
.from(TESTB)
.where(TESTB.ID.eq(studentId))
);
What is the uses of setNull() method in PreparedStatement interface? I looked in this post.
It says: Without the setNull(..) method there would be no way to set null values for the Java primitives.
however with autoboxing in JDK5, I think null values can be set on even primitive types.
There is another post in some other forum says:If you want to be portable to different databases, use the setNull() method.
However there is nothing clearly mentioned in Java doc. Could you help me understanding this?
I think it's easier to understand this if you view it from the database end. If you want to set a field to NULL in your database insert statement, then you need a way of telling the database that is should be set to NULL rather than the default value for the column. If in the database schema you have a nullable integer field, you would use set null to set it to the DB NULL value, rather than to its default value ( 0 ).