I've project with spring, spring-boot and JPA.
When a user tries to log in I want to register activity in a binnacle.
The authentication is with LDAP
I have a new class called LoginActivity and implement an interface with only one method to save activity with annotation #Component and my method where a want to save information when user put credentials wrong I have annotation
#Transactional(propagation = Propagation.REQUIRES_NEW)
And I have another method where I try to save information in my database
I debug my code and it looks good and the process finished well.
But when I saw my database I don't see anything
I use DTO objects between classes
My method authentication:
#Override
#Transactional
public Authentication authenticate(Authentication authentication) {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
List<GrantedAuthority> authorities = (List<GrantedAuthority>) authentication.getAuthorities();
...
context = connect(user, password);//where authentication did
My DTO class, I use lombok
#Data
#Builder
public class LoginDTO {
private String user;
private String tracking;
private Map<String, Boolean> roles;
private String name;
private String lastName;
private boolean loginSuccess;
private String ipAddress;
}
I set every value in my class DTO
LoginDTO loginDTO = LoginDTO.builder()
.loginSuccess(true)
.tracking(tracking)
.lastName(lastName)
.name(name)
.roles(roles)
.user(user)
.ipAddress(httpServletRequest.getRemoteAddr())
.build();
loginActivity.saveLoginActivity(LoginDTO);
My interface
#FunctionalInterface
public interface LoginActivity {
public void saveLoginActivity(LoginDTO loginDTO);
}
My class than implement interface
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class LoginActivityImpl implements LoginActivity {
My entity
#Entity(name = "activity_desk_control")
#Setter
#Getter
public class ActivityDeskControlEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Basic(optional = false)
#Size(max = 255)
#Column(name = "id")
private String id;
#ManyToOne
#JoinColumn(name = "id_user_desk")
private DeskUserLogEntity idUserDesk;
#Column(name = "creation_date")
private Date creationDate;
#Column(name = "id_tracking")
private String idTracking;
#ManyToOne
#JoinColumn(name = "id_service_desk_control")
private ServiceDeskControlEntity idServiceDeskControl;
#Column(name = "params")
#Lob
private String params;
#Column(name = "url")
private String url;
#Column(name = "ip_address")
private String ipAddress;
#Column(name = "login_success")
private int loginSuccess;
#Column(name = "logout")
private int logout;
#Column(name = "logout_date")
private Date logoutDate;
}
My method where I save activity if authentication was well
public void saveMultipart(ActivityDeskControlEntity activityDeskControlEntity) {
this.activityDeskControlRepository.save(activityDeskControlEntity);
}
My method where I save activity if authentication was wrong
#Transactional(propagation = Propagation.REQUIRES_NEW)
public SimpleResponse saveMultipartLoginFail(ActivityDeskControlEntity activityDeskControlEntity) {
this.activityDeskControlRepository.save(activityDeskControlEntity);
}
Have you some idea how I can save information if I got an exception in JPA?
I look some links like this but not work.
My database is Oracle 19c
Update 1
The exception I get when I put credentials wrong is
javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]
In this scenario I want to save information the login fail.
Update 2
In the scenario that throws an exception is
context = connect(user, password);
For default LDAP throw an exception when user and password are wrong in consequence in this exception I want to save.
Update 3
I saw in documentation says:
Any RuntimeException or Error triggers rollback, and any checked
Exception does not.
When the user put credentials wrong throw an exception that extends RuntimeException
import org.springframework.security.core.AuthenticationException;
/**
* Thrown if an authentication request is rejected because the credentials are invalid.
* For this exception to be thrown, it means the account is neither locked nor disabled.
*
* #author Ben Alex
*/
public class BadCredentialsException extends AuthenticationException {
// ~ Constructors
// ===================================================================================================
/**
* Constructs a <code>BadCredentialsException</code> with the specified message.
*
* #param msg the detail message
*/
public BadCredentialsException(String msg) {
super(msg);
}
/**
* Constructs a <code>BadCredentialsException</code> with the specified message and
* root cause.
*
* #param msg the detail message
* #param t root cause
*/
public BadCredentialsException(String msg, Throwable t) {
super(msg, t);
}
}
/**
* Abstract superclass for all exceptions related to an {#link Authentication} object
* being invalid for whatever reason.
*
* #author Ben Alex
*/
public abstract class AuthenticationException extends RuntimeException {
// ~ Constructors
// ===================================================================================================
/**
* Constructs an {#code AuthenticationException} with the specified message and root
* cause.
*
* #param msg the detail message
* #param t the root cause
*/
public AuthenticationException(String msg, Throwable t) {
super(msg, t);
}
/**
* Constructs an {#code AuthenticationException} with the specified message and no
* root cause.
*
* #param msg the detailed message
*/
public AuthenticationException(String msg) {
super(msg);
}
}
I tried to change type of exception, but I couldn't, why? spring security to expected BadCredentialsException and not my own BadCredentialsException.
Are there any way to achieve that?
The simplest approach would be a try catch statement since the Stacktrace for the exception is missing in your question I ave to guess that your exception is thrown in line
context = connect(user, password);//where authentication did
A solution would then be
try {
context = connect(user, password);//where authentication did
} catch (AuthenticationException e) {
log.error("User could not autheticate");
someRepository.save(CustomErrorObject);
someOtherCustomSaveMethod();
throw e;
}
the error behavior is still the same since the exception is re thrown in the catch statement, but the save code before can be executed.
This is my first time making a discord bot that attached to a DB using spring boot and Gradle. I followed some tutorials and the discord bot is working properly but when I want to call my DB it returns null in my Java. I tried to use the same query in my PHPmyadmin to see if it is really a null but in reality, it should have returned a value.
This is my Main class
#SpringBootApplication
public class Main {
private static Logger logger = LogManager.getLogger(Main.class);
/**
* The entrance point of our program.
*
* #param args The arguments for the program. The first element should be the bot's token.
*/
public static void main(String[] args) {
// if (args.length < 1) {
// System.err.println("Please provide a valid token as the first argument!");
// return;
// }
SpringApplication.run(Main.class, args);
// Enable debugging, if no slf4j logger was found
FallbackLoggerConfiguration.setDebug(true);
// The token is the first argument of the program
String token = "Nzg3NTI4MTU0MDM3MjIzNDU2.X9WQvw.Ix9zeiZB5KWwGkxfUAU0pjy4xF0";
// We login blocking, just because it is simpler and doesn't matter here
DiscordApi api = new DiscordApiBuilder().setToken(token).login().join();
// Print the invite url of the bot
logger.info("You can invite me by using the following url: " + api.createBotInvite());
// Add listeners
api.addMessageCreateListener(new CommandManager("!")); // <-- I want to run this command
// Log a message, if the bot joined or left a server
api.addServerJoinListener(event -> logger.info("Joined server " + event.getServer().getName()));
api.addServerLeaveListener(event -> logger.info("Left server " + event.getServer().getName()));
}
}
From there, it should lead me to my CommandManager.java within my Command directory
public class CommandManager implements MessageCreateListener {
String prefix="!";
DieCommand dieCommand = new DieCommand();
EightBallCommand eightBallCommand = new EightBallCommand();
UserInfoCommand userInfoCommand = new UserInfoCommand();
NijiMemberCommand nijiMemberCommand = new NijiMemberCommand();
CurrencyConverterCommand currencyConverterCommand = new CurrencyConverterCommand();
public CommandManager(String pfx) {
this.prefix = pfx;
}
#Override
public void onMessageCreate(MessageCreateEvent event) throws NullPointerException {
String[] command = event.getMessageContent().split(" ");
if(command[0].contains(prefix+"roll")){
dieCommand.onMessageCreate(event);
return;
}
if(command[0].contains(prefix+"8ball")){
eightBallCommand.onMessageCreate(event);
return;
}
if(command[0].contains(prefix+"userinfo")){
userInfoCommand.onMessageCreate(event);
return;
}
if(command[0].contains(prefix+"whois")){
nijiMemberCommand.onMessageCreate(event);
return;
}
if(command[0].contains(prefix+"convert")){
currencyConverterCommand.onMessageCreate(event);
return;
}
}
}
For this class, I have a configurationManager for it
#Configuration
public class ConfigurationManager {
#Bean
public CommandManager commandManager() {
return new CommandManager("!");
}
}
The command that I want to test is !whois noraneko which will lead me to NijiMemberCommand.java
#Component
public class NijiMemberCommand implements MessageCreateListener {
#Autowired
MemberListService memberListService;
private static org.apache.logging.log4j.Logger logger = LogManager.getLogger(Main.class);
#Override
public void onMessageCreate(MessageCreateEvent event){
String[] command = event.getMessageContent().split(" ");
if (command.length == 1) {
logger.info("Will it enter here? no?");
event.getChannel().sendMessage("You are missing argument, the command is consist of !member <name of liver>");
return;
}
event.getChannel().sendMessage(memberListService.visual(command[1]));
logger.info("Please enter here..... " + command[1] + " " + memberListService.visual(command[1]));
event.getChannel().sendMessage(memberListService.visual(command[1]));
return;
}
}
Until this point, there is nothing wrong until it reached to event.getChannel().sendMessage(memberListService.visual(command[1])); which in my mind it should lead me to my Service layer which is this
#Service
public class MemberListServiceImpl implements MemberListService {
#Autowired
MemberListDB memberListDB;
private static org.apache.logging.log4j.Logger logger = LogManager.getLogger(Main.class);
public String capitalize(String name){
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
#Override
#Transactional
public String visual(String nama) {
logger.info("Masuk "+ nama);
String[] name = nama.split(" ");
String result =null;
for(String e : name){
result+=capitalize(e)+" ";
}
return memberListDB.showVisual(result);
}
}
and this is my DB
#Repository
#EnableJpaRepositories
public interface MemberListDB extends JpaRepository<MemberListModel, Long> {
#Query(value = "select m.visual FROM member_list as m, nickname AS n WHERE n.nick_id = m.nick AND n.nickname=:w", nativeQuery = true)
String showVisual(#Param("w") String nama);
}
I tried to run a similar query within my PHPmyadmin, it shows non-null value
Where did I go wrong?
// Edit
This is my model that I use for my program
package com.nijicord.nijiworld.db.Model;
import com.nijicord.nijiworld.db.Repository.MemberListDB;
import com.sun.istack.NotNull;
import javax.persistence.*;
import java.io.Serializable;
/**
* This is the table model that will hold the majority of member's info
*/
#Entity
#Table(name = "member_list")
public class MemberListModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "nick")
private Long nick_id;
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "social_media")
private Long social_id;
#NotNull
#Column(name = "name")
private String name;
#NotNull
#Column(name = "branch")
private String branch;
#NotNull
#Column(name = "debut_3d")
private boolean debut;
#Column(name = "illustrator")
private String illustrator;
#NotNull
#Column(name = "visual")
private String visual;
public Long getNick_id() {
return nick_id;
}
public void setNick_id(Long nick_id) {
this.nick_id = nick_id;
}
public Long getSocial_id() {
return social_id;
}
public void setSocial_id(Long social_id) {
this.social_id = social_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBranch() {
return branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public boolean isDebut() {
return debut;
}
public void setDebut(boolean debut) {
this.debut = debut;
}
public String getIllustrator() {
return illustrator;
}
public void setIllustrator(String illustrator) {
this.illustrator = illustrator;
}
public String getVisual() {
return visual;
}
public void setVisual(String visual) {
this.visual = visual;
}
}
////Edit
This is the error
2020-12-20 15:37:54.405 ERROR 11840 --- [utorService - 1] o.j.core.util.event.EventDispatcherBase : Unhandled exception in a listener thread for FFJ THREAD EMPORIUM!
java.lang.NullPointerException: null
at com.nijicord.nijiworld.command.NijiMemberCommand.onMessageCreate(NijiMemberCommand.java:27) ~[main/:na]
at com.nijicord.nijiworld.command.CommandManager.onMessageCreate(CommandManager.java:44) ~[main/:an]
//////Edit
I have edited my original post with the flow and the code.
You are using nama.toUpperCase() here.
#Override
#Transactional
public String visual(String nama) {
logger.info("Masuk "+ nama);
return memberListDB.showVisual(nama.toUpperCase());
}
But in query you are using nickname in lowercase e.g. noraneko. I think this might be a problem. You are using uppercase instead of lowercase.
We are facing a strange problem occurring within our application using eclipselink. We built a multi-tenancy application which keeps several tenants within one table, separated by a tenancy row. The tenant and a separate entityid form the composite key for each entry. We configured the shared cache and everything works well for most of the time. However, sooner or later while using the application, we face situations where eager loading of related entities does not always work properly. The problem is shown in the following example:
The application loads macroallocations by the id of another entity:
#Entity
#Multitenant(value=MultitenantType.SINGLE_TABLE)
#TenantDiscriminatorColumn(name = DatabaseBindingIds.MACROALLOCATION_TENANT, contextProperty = MULTITENANT_PROPERTY_DEFAULT, primaryKey = true)
#Cache(type=CacheType.SOFT,size=100000)
public class MacroAllocation extends ReadWriteRecord {
/**
* The entity id of this macroallocation.
*/
#Id
#Column(name=DatabaseBindingIds.MACROALLOCATION_ID)
private String entityId;
/**
* The phase this macroallocation belongs to.
*/
#ManyToOne(fetch=FetchType.EAGER, optional=false)
//#BatchFetch(BatchFetchType.JOIN)
#JoinColumn(name=DatabaseBindingIds.MACROALLOCATION_PHASE_ID,referencedColumnName=DatabaseBindingIds.PHASE_ID, insertable=true, updatable=true)
private Phase phase;
/**
* The resource this macroallocation is assigned to.
*/
#ManyToOne(fetch=FetchType.EAGER, optional=false)
//#BatchFetch(BatchFetchType.JOIN)
#JoinColumn(name=DatabaseBindingIds.MACROALLOCATION_RESOURCE_ID, referencedColumnName=DatabaseBindingIds.RESOURCE_ID, insertable=true, updatable=true)
private Resource resource;
/**
* The duration of the allocation.
*/
#Column
#Convert(converter = DurationConverter.class)
private Duration duration;
/**
* Get the macroallocation id.
* #exception IllegalStateException EntityId can never be null.
*/
#Override
public String getId() {
if(entityId == null){
throw new IllegalStateException("[Constraint violation] entityId can not be null in " + this.getClass().getSimpleName());
}
return entityId;
}
/**
* Set the full id of this macroallocation.
* #exception IllegalStateException EntityId can never be null.
*/
#Override
public void setId(String entityId) {
markNew();
this.entityId = entityId;
if(entityId == null){
throw new IllegalStateException("[Constraint violation] entityId can not be null in " + this.getClass().getSimpleName());
}
}
/**
* Get the phase to which the macroallocation belongs.
* #exception IllegalStateException Phase can never be null.
*/
public Phase getPhase() {
if(phase == null){
throw new IllegalStateException("[Constraint violation] phase can not be null in " + this.getClass().getSimpleName());
}
return phase;
}
/**
* Set the phase to which the macroallocation belongs.
* #exception IllegalStateException Phase can never be null.
*/
public void setPhase(Phase x) {
phase = x;
if(phase == null){
throw new IllegalStateException("[Constraint violation] phase can not be null in " + this.getClass().getSimpleName());
}
}
/**
* Get the resource this macroallocation is assigned to.
* #exception IllegalStateException Resource can never be null.
*/
public Resource getResource() {
if(resource == null){
throw new IllegalStateException("[Constraint violation] resource can not be null in " + this.getClass().getSimpleName());
}
return resource;
}
/**
* Set the resource this macroallocation is assigned to.
* #exception IllegalStateException Resource can never be null.
*/
public void setResource(Resource x) {
resource = x;
if(resource == null){
throw new IllegalStateException("[Constraint violation] resource can not be null in " + this.getClass().getSimpleName());
}
}
/**
* Get the duration of this macroallocation.
* #return duration - can be null.
*/
public Duration getDuration() {
return duration;
}
/**
* Set the duration of this macroallocation.
* #param duration - can be null.
*/
public void setDuration(Duration x) {
duration = x;
}
}
To populate our application layer object based on the database layer entities, we usually get more information from related entities the following way:
macroAllocation.getPhase().getScenario().getProject().getId()
The phase should be eagerly loaded with the macroallocation and the scenario should be eagerly loaded as can be seen in the definition of the phase below:
#Entity
#Multitenant(value=MultitenantType.SINGLE_TABLE)
#TenantDiscriminatorColumn(name = DatabaseBindingIds.PHASE_TENANT, contextProperty = MULTITENANT_PROPERTY_DEFAULT, primaryKey = true)
#Cache(type=CacheType.SOFT,size=10000)
public class Phase extends ReadWriteRecord {
/**
* The entity id of this phase.
*/
#Id
#Column(name=DatabaseBindingIds.PHASE_ID)
private String entityId;
#OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.PERSIST) // one to one mappings are directly mapped using the phase primary keys
#BatchFetch(BatchFetchType.JOIN)
#JoinColumn(name=DatabaseBindingIds.PHASE_ID, referencedColumnName=DatabaseBindingIds.PROPERTYSET_ID, insertable=false, updatable=false)
private PropertySet propertySet;
#OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.PERSIST) // one to one mappings are directly mapped using the phase primary keys
#BatchFetch(BatchFetchType.JOIN)
#JoinColumn(name=DatabaseBindingIds.PHASE_ID, referencedColumnName=DatabaseBindingIds.LOGSET_ID, insertable=false, updatable=false)
private LogSet logSet;
#OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.PERSIST) // one to one mappings are directly mapped using the phase primary keys
#BatchFetch(BatchFetchType.JOIN)
#JoinColumn(name=DatabaseBindingIds.PHASE_ID, referencedColumnName=DatabaseBindingIds.COSTSSET_ID, insertable=false, updatable=false)
private CostsSet costsSet;
#OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.PERSIST) // one to one mappings are directly mapped using the phase primary keys
#BatchFetch(BatchFetchType.JOIN)
#JoinColumn(name=DatabaseBindingIds.PHASE_ID, referencedColumnName=DatabaseBindingIds.TODOSET_ID, insertable=false, updatable=false)
private TodoSet todoSet;
#ManyToOne(fetch=FetchType.EAGER, optional=false)
#JoinColumn(name=DatabaseBindingIds.PHASE_SCENARIO_ID, referencedColumnName=DatabaseBindingIds.SCENARIO_ID, insertable=true, updatable=true)
private Scenario scenario;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name=DatabaseBindingIds.PHASE_PARENTPHASE_ID, referencedColumnName=DatabaseBindingIds.PHASE_ID, insertable=true, updatable=true)
private Phase parentPhase;
#Column
private Double sortIndex;
#Column
private String name;
#Column
private String description;
#Column
private String imageUrl;
#Column
#Convert(converter = DurationConverter.class)
private Duration budget;
#Column
#Convert(converter = DurationConverter.class)
private Duration planned;
#Column
#Convert(converter = PointInTimeConverter.class)
private PointInTime.Utc beg;
#Column//(name="\"End\"") // If you think you want to add this, check first why they are not escaped by the EclipseLink SessionCustomizer
#Convert(converter = PointInTimeConverter.class)
private PointInTime.Utc end;
#Column
private Boolean fixed;
#Column
private Integer progress;
#Column
private Boolean autoProgress;
#Column
private String costCenter;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name=DatabaseBindingIds.PHASE_OWNER_ID, referencedColumnName=DatabaseBindingIds.RESOURCE_ID, insertable=true, updatable=true)
private Resource owner;
#Column
private String color;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name=DatabaseBindingIds.PHASE_REQUIRED_SKILL_ID, referencedColumnName=DatabaseBindingIds.RESOURCE_ID, insertable=true, updatable=true)
private Resource requiredSkill;
#OneToMany(mappedBy="fromPhase", fetch=FetchType.LAZY)
private List<PhaseDependency> forwardDependencies;
#OneToMany(mappedBy="toPhase", fetch=FetchType.LAZY)
private List<PhaseDependency> reverseDependencies;
/**
* Get the phase id.
* #exception IllegalStateException EntityId can never be null.
*/
#Override
public String getId() {
if(entityId == null){
throw new IllegalStateException("[Constraint violation] entityId can not be null in " + this.getClass().getSimpleName());
}
return entityId;
}
/**
* Set the full id of this phase.
* #exception IllegalStateException EntityId can never be null.
*/
#Override
public void setId(String entityId) {
markNew();
this.entityId = entityId;
if(entityId == null){
throw new IllegalStateException("[Constraint violation] entityId can not be null in " + this.getClass().getSimpleName());
}
propertySet = new PropertySet();
propertySet.setId(entityId);
logSet = new LogSet();
logSet.setId(entityId);
costsSet = new CostsSet();
costsSet.setId(entityId);
todoSet = new TodoSet();
todoSet.setId(entityId);
}
/**
* Get the property set of the phase.
* #exception IllegalStateException propertySet can never be null.
*/
public PropertySet getPropertySet() {
if(propertySet == null){
throw new IllegalStateException("[Constraint violation] propertySet can not be null in " + this.getClass().getSimpleName());
}
return propertySet;
}
/**
* Get the log set of the phase.
* #exception IllegalStateException logSet can never be null.
*/
public LogSet getLogSet() {
if(logSet == null){
throw new IllegalStateException("[Constraint violation] logSet can not be null in " + this.getClass().getSimpleName());
}
return logSet;
}
/**
* Get the costs set of the phase.
* #exception IllegalStateException costsSet can never be null.
*/
public CostsSet getCostsSet() {
if(costsSet == null){
throw new IllegalStateException("[Constraint violation] costsSet can not be null in " + this.getClass().getSimpleName());
}
return costsSet;
}
/**
* Get the todo set of the phase.
* #exception IllegalStateException todoSet can never be null.
*/
public TodoSet getTodoSet() {
if(todoSet == null){
throw new IllegalStateException("[Constraint violation] todoSet can not be null in " + this.getClass().getSimpleName());
}
return todoSet;
}
/**
* Get the scenario of the phase.
* #exception IllegalStateException scenario can never be null.
*/
public Scenario getScenario() {
if(scenario == null){
throw new IllegalStateException("[Constraint violation] scenario can not be null in " + this.getClass().getSimpleName());
}
return scenario;
}
/**
* Set the scenario of the phase.
* #exception IllegalStateException scenario can never be null.
*/
public void setScenario(Scenario x) {
scenario = x;
if(scenario == null){
throw new IllegalStateException("[Constraint violation] scenario can not be null in " + this.getClass().getSimpleName());
}
}
/**
* Get the parent phase of this phase.
* #return parentPhase - can be null.
*/
public Phase getParentPhase() {
return parentPhase;
}
/**
* Set the parent phase of this phase.
* #return parentPhase - can be null.
*/
public void setParentPhase(Phase x) {
parentPhase = x;
}
/**
* Get the sort index of the phase.
* #return
*/
public double getSortIndex() {
return sortIndex == null ? 0.0 : sortIndex;
}
/**
* Set the sort index of the phase.
* #param x
*/
public void setSortIndex(double x) {
sortIndex = x;
}
/**
* Get the name of the phase.
* #return name - can be null.
*/
public String getName() {
return name;
}
/**
* Set the name of this phase.
* #param name - can be null.
*/
public void setName(String x) {
name = x;
}
/**
* Get the description of the phase.
* #return description - can be null.
*/
public String getDescription() {
return description;
}
/**
* Set the description of this phase.
* #param description - can be null.
*/
public void setDescription(String x) {
description = x;
}
/**
* Get the image url of the phase.
* #return imageUrl - can be null.
*/
public String getImageUrl() {
return imageUrl;
}
/**
* Set the imag url of this phase.
* #param imageUrl - can be null.
*/
public void setImageUrl(String x) {
imageUrl = x;
}
/**
* Get the budget of the phase.
* #return budget - can be null.
*/
public Duration getBudget() {
return budget;
}
/**
* Set the budget of this phase.
* #param budget - can be null.
*/
public void setBudget(Duration x) {
budget = x;
}
/**
* Get the planned duration of the phase.
* #return planned - can be null.
*/
public Duration getPlanned() {
return planned;
}
/**
* Set the planned duration of this phase.
* #param planned - can be null.
*/
public void setPlanned(Duration x) {
planned = x;
}
/**
* Get the beginning of the phase.
* #return beg - can be null.
*/
public PointInTime.Utc getBeg() {
return beg;
}
/**
* Set the beginning of this phase.
* #param beg - can be null.
*/
public void setBeg(PointInTime.Utc x) {
beg = x;
}
/**
* Get the ending of the phase.
* #return end - can be null.
*/
public PointInTime.Utc getEnd() {
return end;
}
/**
* Set the ending of this phase.
* #param end - can be null.
*/
public void setEnd(PointInTime.Utc x) {
end = x;
}
/**
* Get if the phase is fixed.
* #return
*/
public boolean getFixed() {
return fixed == null ? false : fixed;
}
/**
* Set if the phase is fixed.
* #param x
*/
public void setFixed(boolean x) {
fixed = x;
}
/**
* Get the progress of the phase.
* #return
*/
public int getProgress() {
return progress == null ? 0 : progress;
}
/**
* Set the progress of this phase.
* #param
*/
public void setProgress(int x) {
progress = x;
}
/**
* Get if the phase progresses automatically.
* #exception IllegalStateException autoProgress can never be null.
*/
public boolean getAutoProgress() {
return autoProgress == null ? false : autoProgress;
}
/**
* Get if the phase progresses automatically.
* #exception IllegalStateException autoProgress can never be null.
*/
public void setAutoProgress(boolean x) {
autoProgress = x;
}
... not relevant getters and setters...
}
To get proper exceptions when our problem occurs, we added IllegalStateExceptions being thrown that tell us similarly to the db constraints, that an eager loading constraint has been violated. When the problem occurs, we get:
java.lang.IllegalStateException: [Constraint violation] scenario can not be null in Phase
This means that during the loading of the macroallocation, the scenario was not loaded.
The only workaround we found is by accessing the scenario while the database session is still open, but from our understanding, this should only be necessary to load lazy loaded entity relations. What could the problem be? Below are the macroallocation loading method:
#Override
public List<MacroAllocation> loadMacroAllocationsByResource(String resourceId) throws DataAccessException {
try(DatabaseSession session = new DatabaseSession(tenant)) {
List<MacroAllocation> macroAllocations = session.loadByQuery(MacroAllocation.class,
"SELECT ma FROM MacroAllocation AS ma "
+ "WHERE ma.resource.entityId = ?1 " // with matching primary key in resource
+ "AND ma.deleted = 0", // and not deleted
resourceId);
//Workaround: Some objects are not eagerly fetched in some cases. Here we do that explicitly.
macroAllocations.stream().forEach((m) -> {
session.fetch(m.getPhase().getScenario());
session.fetch(m.getPhase().getScenario().getProject());
});
return macroAllocations;
} catch(RuntimeException e) {
throw new DataAccessException(e);
}
}
and the database session we use within this method:
public final class DatabaseSession implements AutoCloseable {
/**
* Maximum latency in milliseconds for a JPA operation, after which a warning shall be logged.
*/
private static final double MAX_LATENCY = 100.0;
/**
* Maximum duration in milliseconds for a session, after which a warning shall be logged.
*/
private static final double MAX_LATENCY_TOT = 1000.0;
/**
* Our logger, never null.
*/
private static final Logger log = LoggerFactory.getLogger(DatabaseSession.class);
/**
* The factory for creating EntityManager instances, created in initEntityManagerFactory() or in the constructor.
*/
private static String persistenceUnitName;
/**
* The EntityManager instance to access the database, created from the factory in the constructor.
*/
private EntityManager em;
/**
* The time when the instance was created, useful for measure total time of the session.
*/
private final long ttot = System.nanoTime();
/**
* Indicates whether commit() as been called.
*/
private boolean committed;
private String tenant;
private static final Cache<String, EntityManagerFactory> emfs = new Cache<>(tenant -> { // create if absent
synchronized (DatabaseSession.class) {
HashMap<String,String> properties = new HashMap<>();
properties.put(SESSION_NAME, (tenant!=null && tenant != "")?tenant+"-session":"non-tenant-session");
properties.put(MULTITENANT_PROPERTY_DEFAULT, tenant);
if(persistenceUnitName == null){
log.debug("Persistence Unit Name defaults to: default");
persistenceUnitName = "default";
}
return Persistence.createEntityManagerFactory(persistenceUnitName, properties);
}
},
(entityManagerFactory) -> { // on remove
entityManagerFactory.close();
});
/**
* Opens a new non-tenant specific session and begins a new transaction.
*
* Only shared, non-tenant specific entities can be retrieved. Multitenancy entities can not be retrieved using this session. To retrieve tenant specific entities, create a tenant session using <b>new DataSession(tenant)</b>
*/
public DatabaseSession() {
this.tenant = "";
synchronized (DatabaseSession.class) {
emfs.get(tenant);
}
createEntityManager();
}
/**
* Opens a new tenant session and begins a new transaction.
*
* Multitenancy entities can be retrieved using this session.
*/
public DatabaseSession(String tenant) {
if(tenant == null || tenant.equals("")){
log.error("Trying to create a non-tenant database session with tenant specific constructor? Use constructor DatabaseSession() instead.");
tenant = "";
}
this.tenant = tenant;
synchronized (DatabaseSession.class) {
emfs.get(tenant); // creates a new factory in a synchronized manner.
}
createEntityManager();
}
/**
* #note: Should only be called publicly by unit tests.
*/
public void createEntityManager() {
em = emfs.get(tenant).createEntityManager();
em.getTransaction().begin();
}
/**
* Initializes the EntityManagerFactory (optional, useful for testing).
* <p>
* If this method is not called, the EntityManagerFactory is initialized automatically with persistence unit "default" when the first instance is created.
* <p>
* Persistence units are defined in conf/META-INF/persistence.xml.
*
* #param persistenceUnitName
* the name of the persistence unit to be used, must match the XML attribute /persistence/persistence-unit/#name.
*/
public static void initEntityManagerFactory(String pun) {
persistenceUnitName = pun;
}
/**
* Sets up all factories to prevent eclipselink from drop and creating the db.
*
* #note: Should only be called by unit tests.
*/
public void setupEntityManagerFactories(List<String> tenants) {
log.warn("SetupEntityManagerFactories should only be called by Unit tests.");
for(String tenant: tenants){
emfs.get(tenant);
}
}
/**
* Closes the connection to the database completely. For Unit tests, this drops and recreates the database to advance to the next unit test with a fresh one.
*
* #note: Should only be called by unit tests.
*/
public void shutdownDB() {
log.warn("ShutdownDB should only be called by Unit tests.");
em.close();
em = null;
DatabaseSession.emfs.clear(); // closes entity manager factory on close
}
#Override
public void close() {
try {
if (!committed) {
if (em != null) {
em.getTransaction().rollback();
}
}
} finally {
if (committed) {
if (em != null) {
em.close();
}
}
double latency = (System.nanoTime() - ttot) / 1000000.0;
if (latency > MAX_LATENCY_TOT) {
log.warn("Duration of session was " + latency + "ms.");
} else {
log.debug("Duration of session was " + latency + "ms.");
}
}
}
/**
* Commits the transaction, must explicitly be done before the session is closed.
*/
public void commit() {
long t = System.nanoTime();
em.flush();
em.getTransaction().commit();
committed = true;
double latency = (System.nanoTime() - t) / 1000000.0;
if (latency > MAX_LATENCY) {
warn("Latency of commit() was %sms.", latency);
}
}
public <T extends PersistentRecord> List<T> loadAll(Class<T> clazz) {
return loadAll(clazz, true);
}
public <T extends PersistentRecord> List<T> loadAll(Class<T> clazz, boolean filterDeleted) {
log("loadAll(%s)", clazz.getSimpleName());
long t = System.nanoTime();
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<T> q = b.createQuery(clazz);
Metamodel m = em.getMetamodel();
EntityType<T> et = m.entity(clazz);
Root<T> r = q.from(clazz);
q.select(r);
if (filterDeleted) {
q.where(b.equal(r.get(et.getAttribute("deleted").getName()), 0));
}
List<T> results = em.createQuery(q).getResultList();
double latency = (System.nanoTime() - t) / 1000000.0;
if (latency > MAX_LATENCY) {
warn("Latency of loadAll(%s) was %sms.", clazz.getSimpleName(), latency);
}
return results;
}
public <T extends PersistentRecord> int count(Class<T> clazz) {
return count(clazz, true);
}
public <T extends PersistentRecord> int count(Class<T> clazz, boolean filterDeleted) {
log("count(%s)", clazz.getSimpleName());
long t = System.nanoTime();
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<T> q = b.createQuery(clazz);
Metamodel m = em.getMetamodel();
EntityType<T> et = m.entity(clazz);
Root<T> r = q.from(clazz);
q.select(r);
if (filterDeleted) {
q.where(b.equal(r.get(et.getAttribute("deleted").getName()), 0));
}
List<T> result = em.createQuery(q).getResultList();
double latency = (System.nanoTime() - t) / 1000000.0;
if (latency > MAX_LATENCY) {
warn("Latency of count(%s) was %sms.", clazz.getSimpleName(), latency);
}
return result.size();
}
public <T extends PersistentRecord> T load(Class<T> clazz, String id) {
return load(clazz, id, true);
}
public <T extends PersistentRecord> T load(Class<T> clazz, String id, boolean filterDeleted) {
log("load(%s, %s)", clazz.getSimpleName(), id);
long t = System.nanoTime();
T result = em.find(clazz, id);
if (filterDeleted) {
result = filterDeleted(result);
}
double latency = (System.nanoTime() - t) / 1000000.0;
if (latency > MAX_LATENCY) {
warn("Latency of load(%s, %s) was %sms.", clazz.getSimpleName(), id, latency);
}
return result;
}
public <T extends PersistentRecord> List<T> loadByQuery(Class<T> clazz, String query, Object... params) {
log("loadByQuery(%s, '%s', %s)", clazz.getSimpleName(), query, format(params));
long t = System.nanoTime();
TypedQuery<T> q = em.createQuery(query, clazz);
for (int i = 0; i < params.length; i++) {
q.setParameter(i + 1, params[i]);
}
List<T> result = q.getResultList();
result = filterDeleted(result);
double latency = (System.nanoTime() - t) / 1000000.0;
if (latency > MAX_LATENCY) {
warn("Latency of loadByQuery(%s, '%s', %s) was %sms.", clazz.getSimpleName(), query, format(params), latency);
}
return result;
}
public <T extends PersistentRecord> T loadSingleByQuery(Class<T> clazz, String query, Object... params) {
log("loadSingleByQuery(%s, '%s', %s)", clazz.getSimpleName(), query, format(params));
long t = System.nanoTime();
TypedQuery<T> q = em.createQuery(query, clazz);
for (int i = 0; i < params.length; i++) {
q.setParameter(i + 1, params[i]);
}
List<T> result = q.getResultList();
result = filterDeleted(result);
double latency = (System.nanoTime() - t) / 1000000.0;
if (latency > MAX_LATENCY) {
warn("Latency of loadSingleByQuery(%s, '%s', %s) was %sms.", clazz.getSimpleName(), query, format(params), latency);
}
return result.size() > 0 ? result.get(0) : null;
}
... storing methods not relevant here...
}
I have a form that is representing a Role object. This role object can have one System object, which is selected via a drop-down list (form:select). It works perfectly except for one little snag: when editing the Role object the System object is not automatically selected on the list. From what I understand, it should be. Can anyone tell me why it isn't? Code is as follows:
Role class:
/**
* Represents a Role in the Database. Used for tracking purposes it allows us to
* find out what users and systems have certain roles. Role entity. #author
* MyEclipse Persistence Tools
*/
#Entity
#Table(name = "roles", catalog = "jess")
public class Role implements java.io.Serializable {
// Fields
private static final long serialVersionUID = -8599171489389401780L;
private Integer roleId;
#Valid
private System system;
...
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "SYSTEM_ID")
public System getSystem() {
return this.system;
}
public void setSystem(System system) {
this.system = system;
}
Controller:
#RequestMapping(value = "/" + MappingConstants.EDIT_ROLE + "/{id}",
method = RequestMethod.POST)
public ModelAndView getEditRoleForm(#PathVariable("id") Integer id)
{
Role r = new Role();
r.setRoleId(id);
Role role = roleService.searchAllRolesByID(r);
ModelAndView modelView = new ModelAndView(MappingConstants.ROLES_FOLDER + MappingConstants.EDIT_ROLE);
modelView.addObject(AttributeConstants.ROLE, role);
List<System> systems = systemService.searchAllSystems();
modelView.addObject(AttributeConstants.ALL_SYSTEMS, systems);
return modelView;
}
Property Editor:
public class SystemEditor extends PropertyEditorSupport
{
private final ISystemService systemService;
private static Logger logger = LogManager.getLogger(SystemEditor.class.getName());
public SystemEditor(ISystemService service)
{
super();
this.systemService = service;
}
/*
* (non-Javadoc)
* #see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
*/
public void setAsText(String text) throws IllegalArgumentException
{
try
{
if(logger.isDebugEnabled())
logger.debug("System value coming in the editor as: {}", text);
System system = systemService.searchAllSystemsById(Integer.valueOf(text));
setValue(system);
}
catch (Exception e)
{
logger.error("There was an error attempting to process the System from the Editor.", e);
}
}
/*
* (non-Javadoc)
* #see java.beans.PropertyEditorSupport#getAsText()
*/
public String getAsText()
{
System system = (System) getValue();
return system.getSystemId().toString();
}
}
And jsp:
<form:form method="post" action="${contextPath}/jess/saveeditedrole" modelAttribute="role">
<h2>${role.name}</h2>
<br/><br/>
<form:errors path="system"/>
<form:label path="system">System:</form:label>
<form:select path="system">
<form:options items="${systems}" itemValue="systemId" itemLabel="fullName"/>
</form:select>
In your form:select you're using System class. Make sure this class has a proper .equals() and hashCode() methods, otherwise Spring doesn't know how to tell which System object is selected.
I have a weird problem. I'm using play 2.1-SNAPSHOT with ebeans (=> mysql). I have a very small (test) setup and for some reason database updates and deletions don't work. Items are created in the DB... but updating them does not work.
Here's my bean (which extends a superclass that adds the timestamps (created and modified date)):
AbstractTimestamp (superclass):
#MappedSuperclass
public abstract class AbstractTimestampedBean extends AbstractIdentifiableBean {
/** The date this item has been created. */
#CreatedTimestamp
public Timestamp createdTime;
}
Project Bean (removed unimportant stuff) - hashCode and equals have been created by eclipse - here we overwrite the methods of play.db.ebean.Model:
#Entity
#Table(name = "Projects")
public class Project extends AbstractTimestampedBean {
private static final long serialVersionUID = -6160140283947231026L;
#NotNull
public String title;
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public User owner;
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public User creator;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Set<User> participants;
#EnumMapping(nameValuePairs = "ACTIVE=A,INACTIVE=I,EXPIRED=E")
public enum Status {
ACTIVE, INACTIVE, EXPIRED
}
public Project() {
}
public Project(final String title, final User creator) {
this.title = title;
this.creator = creator;
this.owner = creator;
}
/*
* (non-Javadoc)
*
* #see play.db.ebean.Model#hashCode()
*/
#Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ (this.creator == null ? 0 : this.creator.hashCode());
result = prime * result
+ (this.owner == null ? 0 : this.owner.hashCode());
result = prime * result
+ (this.participants == null ? 0 : this.participants
.hashCode());
result = prime * result
+ (this.title == null ? 0 : this.title.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* #see play.db.ebean.Model#equals(java.lang.Object)
*/
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final Project other = (Project) obj;
if (this.creator == null) {
if (other.creator != null) {
return false;
}
} else if (!this.creator.equals(other.creator)) {
return false;
}
if (this.owner == null) {
if (other.owner != null) {
return false;
}
} else if (!this.owner.equals(other.owner)) {
return false;
}
if (this.participants == null) {
if (other.participants != null) {
return false;
}
} else if (!this.participants.equals(other.participants)) {
return false;
}
if (this.title == null) {
if (other.title != null) {
return false;
}
} else if (!this.title.equals(other.title)) {
return false;
}
return true;
}
Here's the very simple test case:
First run creates a projects - checks that it's there (nothing fails here)
Then we update some stuff - store it - and assert again... and here I can see that the db entries have not been updated.
http://pastebin.com/7zdzWGXw
Here's the superclass that we are using here:
public abstract class AbstractPersistableTestCase {
#Transactional
void saveBean(final Model bean) {
Ebean.save(bean);
}
#Transactional
void deleteBean(final Model bean) {
Ebean.delete(bean);
}
#Transactional
<T extends Model> void deleteBeans(final List<T> beans) {
Ebean.delete(beans);
}
}
Error message from jUnit4:
This is the assertion of the title in the update case => See: db entry has not been updated:
[error] Test test.models.ProjectTest.createAndUpdateProject failed: expected:<'Project_[NEW_]1350681993608'> but was:<Project_[]1350681993608'>
This happens when I try to delete the project:
[error] Test test.models.ProjectTest.deleteProjects failed: Data has changed. updated [0] rows sql[delete from user where id=? and name is null and email is null and created_time is null] bind[null]
Do you guys have an idea why this is happening? I'm really frustrated here...
Regards,
Sascha
It seems to me that you are not adding an Id to your classes.
Try to add this to your superclass:
#MappedSuperclass
public abstract class AbstractModel extends play.db.ebean.Model
{
#Id
public Long id;
public Long getId()
{
return id;
}
// ... here your other attributes
}