Persistência em duas tabelas com relacionamento @OneToOne

Boa noite.

Estou com uma dúvida e visitei vários fóruns mas infelizmente não encontrei o que preciso, ou se encontrei acabei não entendendo muito bem o funcionamento.

Bom vamos lá, estou trabalhando com jsf, primefaces, spring security e jpa, configurado e funcionando perfeitamente. Tenho uma tabela “funcionario” em um relacionamento @OneToOne com a tabela usuarioLogin. Todos os funcionario devem ser cadastrados, o usuarioLogin deve ser cadastrado apenas a quem ter a necessidade de utilizar o sistema, então o usuarioLogin será cadastrado após o cadastro de funcionario. Acontece que ainda não sei como devo fazer a persistência, as perguntas são, qual deve ser o lado fraco do relacionamento, e, se o lado fraco for a tabela usuarioLogin como devo fazer a persistência do usuarioLogin e da chave estrangeira de funcionario ao mesmo tempo. Se ajudar no entendimento, acesso a tela de consulta funcionario listada em DataTable e lá tenho um link de cadastro/alteração de usuarioLogin com o button salvar usuarioLogin.

Agradeço aos que puderem me ajudar.
[]s

A pergunta é: quem é que “tem” quem? Entendo que o sistema é destinado a usuários e cada usuário POSSUI seu login, não?

Se o usuário já for cadastrado, você precisa recuperar o mesmo para criar o usuário login, não? Usuário login receberá a PK do usuario em uma de suas colunas.

Bom dia, obrigado darlan_machado por me ajudar.

Fiz da forma que me orientou, as informações estão sendo salvas no banco porém ainda preciso corrigir dois pontos, primeiro, ao clicar no button para ir pra página de cadastrar/alterar loginUsuario não está me retornando os dados da tabela usuarioLogin para preencher os input na tela, acredito que o erro esteja nessa linha<o:viewParam name="funcionario" value="#{cadastroUsuarioLoginBean.usuarioLogin.funcionario}" /> pois funcionario é chave estrangeira e não primária, será por isso mesmo? Se sim como resolvo?

Segundo, o usuarioLogin sempre é criado e não alterado quando ele já existe, penso que é devido à primeira questão mencionada acima.

Alguem poderia me dizer como faço pra resolver essas duas questões?

Obrigado!

[]s

Segue os arquivos:

Classe UsuarioLogin

@Entity
@Table(name = "USUARIOLOGIN")
public class UsuarioLogin implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@SequenceGenerator(name="seq7", initialValue=1, allocationSize=100)

private Long id;

private String username;

private String password;

private boolean enable;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "USULOG_NIVAUT", joinColumns = @JoinColumn(name = "USU_id"), inverseJoinColumns = @JoinColumn(name = "NIVAUT_id"))
private List<NivelAutoridade> niveisAutoridade;

@OneToOne
private Funcionario funcionario;	

Classe Funcionario:

@Entity
@Table(name = "funcionario")
public class Funcionario implements Serializable {
private static final long serialVersionUID = 1L;

@NotNull
@ManyToOne(optional = false)
@JoinColumn(name = "profissao_id", referencedColumnName = "id")
private Profissao profissao;

@NotNull
@ManyToOne(optional = false)
@JoinColumn(name = "FuncionarioSituacao_id", referencedColumnName = "id")
private FuncionarioSituacao funcionarioSituacao;

@NotNull
@ManyToOne(optional = false)
@JoinColumn(name = "NivelAutoridade_id", referencedColumnName = "id")
private NivelAutoridade nivelAutoridade;

@OneToOne(mappedBy="funcionario")
private UsuarioLogin usuarioLogin;

@Id
@GeneratedValue
private Long id;

private String nome;

private String funcao;

Controller CadastroUsuarioBean:

@Named
@javax.faces.view.ViewScoped
public class CadastroUsuarioLoginBean implements Serializable {
private static final long serialVersionUID = 1L;

@Inject
private CadastroUsuariosLogin service;

private UsuarioLogin model = new UsuarioLogin();



private String tab;

public void descricaoModificada(ValueChangeEvent event) {
	System.out.println("Valor antigo: " + event.getOldValue());
	System.out.println("Novo valor: " + event.getNewValue());
	FacesContext.getCurrentInstance().renderResponse();
}

public void prepararCadastro() {
	//this.todosCodigosOcorrencia = this.codigosOcorrencia.todos();
	if (this.model == null) {
		this.model = new UsuarioLogin();
	}
}

public String salvar() {
	FacesContext context = FacesContext.getCurrentInstance();
	try {
		this.service.salvar(this.model);
		this.model = new UsuarioLogin();
		context.addMessage(null, new FacesMessage("Contato salvo com sucesso!"));
	} catch (NegocioException e) {
		FacesMessage mensagem = new FacesMessage(e.getMessage());
		mensagem.setSeverity(FacesMessage.SEVERITY_ERROR);
		context.addMessage(null, mensagem);
	}
	return "/CadastroUsuarioLogin?faces-redirect=true";
}


public UsuarioLogin getUsuarioLogin() {
	return model;
}

public void setUsuarioLogin(UsuarioLogin model) {
	this.model = model;
}

}

Repositório UsuariosLogin:

public class UsuariosLogin implements Serializable {
private static final long serialVersionUID = 1L;
private EntityManager manager;

@Inject
public UsuariosLogin(EntityManager manager) {
	this.manager = manager;
}

public List<UsuarioLogin> todos() {
	TypedQuery<UsuarioLogin> query = manager.createQuery("from UsuarioLogin",      UsuarioLogin.class);
	return query.getResultList();
}

public void adicionar(UsuarioLogin model) {
	this.manager.persist(model);
}

public UsuarioLogin porId(Long id) {
	return manager.find(UsuarioLogin.class, id);
}

public UsuarioLogin guardar(UsuarioLogin model) {
	return this.manager.merge(model);
}

public void remover(UsuarioLogin model) {
	this.manager.remove(model);
}

}

Service CadastroUsuariosLogin:

public class CadastroUsuariosLogin implements Serializable {
private static final long serialVersionUID = 1L;

@Inject
private UsuariosLogin repositorios;

@Transactional
public void salvar(UsuarioLogin model) throws NegocioException {
	this.repositorios.guardar(model);
}

@Transactional
public void excluir(UsuarioLogin model) throws NegocioException {
	model = this.repositorios.porId(model.getId());

	this.repositorios.remover(model);
}

}

Sério que você precisa de uma relação OneToOne bidirecional?
Cada Funcionario pode ter um só login e cada login pertence a um único usuário, logo, UsuarioLogin é um atributo de Funcionario e acabou, você não precisa definir um Funcionario dentro de UsuarioLogin.
Mas, se mesmo assim insistir em seguir desta maneira, lembre-se que você precisa setar tanto o UsuarioLogin no Funcionario quanto o Funcionario no UsuarioLogin, mesmo quando está atualizando as informações, assim o JPA vai entender que não se trata de uma inserção, mas, de uma atualização.
Desta forma, você resolve os dois problemas.

Esse código seria o correto.

E, ao invés disso

Isso

private Funcionario funcionario; //Com os respectivos getter e setter para o JSF encontrar

E esse trecho, não se faz necessário.

Mais uma vez muito obrigado por me ajudar darlan_machado.

Fiz o que você orientou e está dando um erro dizendo que usuarioLogin está null. nos inputs estou fazendo assim value="#{cadastroUsuarioLoginBean.funcionario.usuarioLogin.username}", seria isso mesmo?

Segue a saída:

ADVERTÊNCIA: /CadastroUsuarioLogin.xhtml @36,92 value="#        {cadastroUsuarioLoginBean.funcionario.usuarioLogin.username}": Target Unreachable,  [usuarioLogin] returned null
javax.el.PropertyNotFoundException: /CadastroUsuarioLogin.xhtml @36,92 value="#{cadastroUsuarioLoginBean.funcionario.usuarioLogin.username}": Target Unreachable, [usuarioLogin] returned null

Isso é uma limitação do JSF.
Toda vez que você tem um objeto, como Funcionario, precisa dizer, no construtor ou no @PostConstruct que este cara está instanciado, algo como:

private Funcionario func;

@PostConstruct
public void init() {
    func = new Funcionario();
    func.setUsuarioLogin(new UsuarioLogin());
}

darlan_machado Ainda está acusando que usuarioLogin está null.

Talvez eu tenha feito algo errado no controller, Seguindo suas orientações o controller ficou assim:

@Named
@javax.faces.view.ViewScoped
public class CadastroUsuarioLoginBean implements Serializable {
private static final long serialVersionUID = 1L;

@Inject
private CadastroFuncionarios service;
//private CadastroUsuariosLogin service;

//private UsuarioLogin model = new UsuarioLogin();
private Funcionario funcionario;

@PostConstruct
public void init() {
    funcionario = new Funcionario();
    funcionario.setUsuarioLogin(new UsuarioLogin());
}


public void descricaoModificada(ValueChangeEvent event) {
	System.out.println("Valor antigo: " + event.getOldValue());
	System.out.println("Novo valor: " + event.getNewValue());
	FacesContext.getCurrentInstance().renderResponse();
}

public void prepararCadastro() {
	//this.todosCodigosOcorrencia = this.codigosOcorrencia.todos();
	if (this.funcionario == null) {
		this.funcionario = new Funcionario();
	}
}

public String salvar() {
	FacesContext context = FacesContext.getCurrentInstance();
	try {
		this.service.salvar(this.funcionario);
		this.funcionario = new Funcionario();
		context.addMessage(null, new FacesMessage("Contato salvo com sucesso!"));
	} catch (NegocioException e) {
		FacesMessage mensagem = new FacesMessage(e.getMessage());
		mensagem.setSeverity(FacesMessage.SEVERITY_ERROR);
		context.addMessage(null, mensagem);
	}
	return "/CadastroUsuarioLogin?faces-redirect=true";
}

public Funcionario getFuncionario() {
	return funcionario;
}

public void setFuncionario(Funcionario funcionario) {
	this.funcionario = funcionario;
}

}

Não se trata de nulo, se trata de não encontrar um atributo chamado usuarioLogin.
Reveja tua classe Funcionario, verifique se existe um getter e um setter conforme padrão javaBeans.

Rapaz, todos os getter e setter estão lá, o init() com a anotação @PostConstruct está iniciando normalmente porém o erro persiste, tem mais alguma ideia de onde o erro possa estar vindo? Pensei que pudesse ser o escopo, testei e não adiantou.

Grato!

Bom dia pessoal, peço a ajuda de vocês para eu resolver este problema, acredito que esteja perto da solução.

No @PostConstruct fazendo funcionario.getUsuarioLogin().getUsername() está retornando null mas pelo que entendi é pra retornar null mesmo, certo? Se sim acredito que o erro esteja no método salvar que segue:

Alguém sabe o que está ocorrendo de errado? Por favor pessoal me ajudem a resolver esse erro.

Obrigado a todos.

public String salvar() {
	FacesContext context = FacesContext.getCurrentInstance();
	try {
		this.service.salvar(this.funcionario);
		this.funcionario = new Funcionario();
		context.addMessage(null, new FacesMessage("Contato salvo com sucesso!"));
	} catch (NegocioException e) {
		FacesMessage mensagem = new FacesMessage(e.getMessage());
		mensagem.setSeverity(FacesMessage.SEVERITY_ERROR);
		context.addMessage(null, mensagem);
	}
	return "/CadastroUsuarioLogin?faces-redirect=true";
}

Veja, você não garantiu que funcionário possui um LoginUsuario, pois não persistiu o LoginUsuario antes de persistir o Funcionario.
Provavelmente isso esteja causando o teu problema.

Que estranho, fiz dessa forma:

public String salvar() {
	FacesContext context = FacesContext.getCurrentInstance();
	try {
		this.serviceUsuariosLogin.salvar(usuarioLogin);
		this.service.salvar(this.funcionario);
		
		this.usuarioLogin = new UsuarioLogin();
		this.funcionario = new Funcionario();
		context.addMessage(null, new FacesMessage("Contato salvo com sucesso!"));
	} catch (NegocioException e) {
		FacesMessage mensagem = new FacesMessage(e.getMessage());
		mensagem.setSeverity(FacesMessage.SEVERITY_ERROR);
		context.addMessage(null, mensagem);
	}
	return "/CadastroUsuarioLogin?faces-redirect=true";
}

Porém o erro permanece. Há algum problema em eu usar 4 níveis na el da forma que estou fazendo no input value="#{cadastroUsuarioLoginBean.funcionario.usuarioLogin.username} ?

Até onde sei, não existe limitação.

Porém, eu faria

this.serviceUsuariosLogin.salvar(usuarioLogin);
funcionario.setUsuarioLogin(usuarioLogin);//Já persistiu, deve conter um id associado
this.service.salvar(this.funcionario);

Em que momento essa linha é chamada? Pois, se você faz um select e carrega só o funcionário, o usuarioLogin realmente será nulo, justificando a mensagem de exceção.
O ideal é, como eu já sugeri, fazer o debug do código e verificar o que existe e o que falta. Facilitando a identificação do erro.

Boa tarde.

Depois, de quebrar bastante a cabeça com este ultimo erro decidi atualizar os repositórios do pom.xml e funcionou perfeitamente. Este projeto é um pouco antigo e agora estou implementando melhorias.

Muito obrigado darlan_machado por sua ajuda, foi de grande valia.

Forte abraço!