Skip to content

Commit

Permalink
Make supported naming strategies available at runtime for native image (
Browse files Browse the repository at this point in the history
#2395)

* Make supported naming strategies available at runtime for native image

* Add note about naming strategy TypeHint

* Protect code with try/catch and log potential error

* Use parameter for log message
  • Loading branch information
radovanradic authored Jul 21, 2023
1 parent 8e73067 commit 7aa7ac6
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationMetadataProvider;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.reflect.InstantiationUtils;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.data.model.naming.NamingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Optional;
Expand All @@ -35,6 +39,8 @@
*/
public abstract class AbstractPersistentEntity implements PersistentEntity {

private static final Logger LOG = LoggerFactory.getLogger(AbstractPersistentEntity.class);

private static final Map<String, NamingStrategy> NAMING_STRATEGIES = new ConcurrentHashMap<>(3);

private final AnnotationMetadataProvider annotationMetadataProvider;
Expand Down Expand Up @@ -70,6 +76,21 @@ private static Optional<NamingStrategy> getNamingStrategy(String className, Clas
if (namingStrategy != null) {
return Optional.of(namingStrategy);
} else {
try {
Class<?> namingStrategyClass = ClassUtils.forName(className, classLoader).orElse(null);
if (namingStrategyClass != null) {
BeanIntrospection<?> beanIntrospection = BeanIntrospection.getIntrospection(namingStrategyClass);
Object o = beanIntrospection.instantiate();
if (o instanceof NamingStrategy ns) {
NAMING_STRATEGIES.put(className, ns);
return Optional.of(ns);
}
}
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Tried, but could not instantiate naming strategy: {}", className, e);
}
}
Object o = InstantiationUtils.tryInstantiate(className, classLoader).orElse(null);
if (o instanceof NamingStrategy ns) {
NAMING_STRATEGIES.put(className, ns);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.micronaut.data.model.naming;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.TypeHint;
import io.micronaut.core.naming.NameUtils;

import java.util.Locale;
Expand All @@ -30,6 +31,7 @@ public class NamingStrategies {
/**
* Example: FOO_BAR.
*/
@TypeHint(UnderScoreSeparatedUpperCase.class)
public static class UnderScoreSeparatedUpperCase implements NamingStrategy {
@NonNull
@Override
Expand All @@ -40,6 +42,7 @@ public String mappedName(@NonNull String name) {
/**
* Example: foo_bar.
*/
@TypeHint(UnderScoreSeparatedLowerCase.class)
public static class UnderScoreSeparatedLowerCase implements NamingStrategy {
@NonNull
@Override
Expand All @@ -50,6 +53,7 @@ public String mappedName(@NonNull String name) {
/**
* Example: foo-bar.
*/
@TypeHint(KebabCase.class)
public static class KebabCase implements NamingStrategy {
@NonNull
@Override
Expand All @@ -60,6 +64,7 @@ public String mappedName(@NonNull String name) {
/**
* Example: foobar.
*/
@TypeHint(LowerCase.class)
public static class LowerCase implements NamingStrategy {
@NonNull
@Override
Expand All @@ -70,6 +75,7 @@ public String mappedName(@NonNull String name) {
/**
* Example: foobar.
*/
@TypeHint(UpperCase.class)
public static class UpperCase implements NamingStrategy {
@NonNull
@Override
Expand All @@ -80,6 +86,7 @@ public String mappedName(@NonNull String name) {
/**
* No naming conversion.
*/
@TypeHint(Raw.class)
public static class Raw implements NamingStrategy {
@NonNull
@Override
Expand Down
29 changes: 15 additions & 14 deletions doc-examples/jdbc-example-java/src/main/java/example/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
package example;

import io.micronaut.data.annotation.*;
import io.micronaut.data.model.naming.NamingStrategies;

@MappedEntity
@Where("@.enabled = true") // <1>
@MappedEntity(namingStrategy = NamingStrategies.Raw.class)
@Where("@.userEnabled = true") // <1>
public class User {
@GeneratedValue
@Id
private Long id;
private String name;
private boolean enabled = true; // <2>
private String userName;
private boolean userEnabled = true; // <2>

public User(String name) {
this.name = name;
public User(String userName) {
this.userName = userName;
}

public Long getId() {
Expand All @@ -24,19 +25,19 @@ public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
public String getUserName() {
return userName;
}

public void setName(String name) {
this.name = name;
public void setUserName(String userName) {
this.userName = userName;
}

public boolean isEnabled() {
return enabled;
public boolean isUserEnabled() {
return userEnabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
public void setUserEnabled(boolean userEnabled) {
this.userEnabled = userEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
public interface UserRepository extends CrudRepository<User, Long> { // <1>

@Override
@Query("UPDATE user SET enabled = false WHERE id = :id") // <2>
@Query("UPDATE user SET userEnabled = false WHERE id = :id") // <2>
void deleteById(@NonNull @NotNull Long id);

@Query("SELECT * FROM user WHERE enabled = false") // <3>
@Query("SELECT * FROM user WHERE userEnabled = false") // <3>
List<User> findDisabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ void testSoftDelete() {

final List<User> disabled = userRepository.findDisabled();
assertEquals(1, disabled.size());
assertEquals("Joe", disabled.iterator().next().getName());
assertEquals("Joe", disabled.iterator().next().getUserName());
}
}
2 changes: 2 additions & 0 deletions src/main/docs/guide/dbc/sqlMapping/sqlNaming.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ include::data-tck/src/main/java/io/micronaut/data/tck/entities/CountryRegion.jav

Few important things to note. Since Micronaut Data pre-computes the table and column name mappings at compilation time the specified api:data.model.naming.NamingStrategy[] implementation must be on the annotation processor classpath (`annotationProcessor` scope for Java or `kapt` for Kotlin).

If running project in native image, custom naming strategy needs to have `io.micronaut.core.annotation.TypeHint(CustomNamingStrategy.class)` annotation where custom naming strategy class is `CustomNamingStrategy`.

In addition if you don't want to repeat the above annotation definition on every entity it is handy to define a meta-annotation where the above annotation definition is applied to another annotation that you add to your class.

==== Escaping Table/Column Name Identifiers
Expand Down

0 comments on commit 7aa7ac6

Please sign in to comment.